补充题搜集
- 1、排序奇升偶降链表
- 思路
- 代码
- 2、36进制加法/减法
- 加法
- 代码
- 减法
- 3、木头切割问题
- 思路
- 代码
- 4、迷宫问题
- 代码
- 5、求区间最小数乘区间和的最大值
- 思路
- 代码
- 6、求一个数的立方根
- 思想
- 代码
- 7、阿拉伯数字转中文数字
- 思路
- 代码
- 8、双栈排序
- 思路
- 代码
- 9、反转双向链表
- 代码
- 10、小和问题
- 思路
- 代码
- 11、两个有序数组的第K小数
- 思路
- 代码
1、排序奇升偶降链表
给定一个奇数位升序,偶数位降序的链表,将其重新排序。
输入: 1->8->3->6->5->4->7->2->NULL
输出: 1->2->3->4->5->6->7->8->NULL
思路
- 按奇偶位置拆分链表,得1->3->5->7->NULL和8->6->4->2->NULL
- 反转偶链表,得1->3->5->7->NULL和2->4->6->8->NULL
- 合并两个有序链表,得1->2->3->4->5->6->7->8->NULL
代码
/**
* Created by yazai
* Date: 下午3:10 2021/8/31
* Description:
*/
public class t {
public static class ListNode {
int val;
ListNode next;
public ListNode() {
}
public ListNode(int n) {
this.val = n;
}
}
//分离奇偶节点并重新合并
public static ListNode sort(ListNode head) {
ListNode oddHead = head, evenHead = head.next;
ListNode oddCur = oddHead, evenCur = evenHead;
while (oddCur.next != null && evenCur.next != null) {
oddCur.next = evenCur.next;
oddCur = oddCur.next;
evenCur.next = oddCur.next;
evenCur = evenCur.next;
}
//当节点数为偶数个时,倒数第二节点.next指向倒数第一节点,但需要指向null
if (oddCur.next != null) {
oddCur.next = null;
}
return merge(oddHead, reverse(evenHead));
}
//反转链表
public static ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
//合并两个【升序】链表
public static ListNode merge(ListNode head1, ListNode head2) {
ListNode dummy = new ListNode();
ListNode cur1 = head1, cur2 = head2, cur3 = dummy;
while (cur1 != null && cur2 != null) {
if (cur1.val < cur2.val) {
cur3.next = cur1;
cur1 = cur1.next;
} else {
cur3.next = cur2;
cur2 = cur2.next;
}
cur3 = cur3.next;
}
if (cur1 != null) {
cur3.next = cur1;
}
if (cur2 != null) {
cur3.next = cur2;
}
return dummy.next;
}
public static void main(String[] args) {
t test = new t();
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(8);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(6);
ListNode n5 = new ListNode(5);
ListNode n6 = new ListNode(4);
ListNode n7 = new ListNode(7);
ListNode n8 = new ListNode(2);
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n5;
n5.next = n6;
n6.next = n7;
n7.next = n8;
ListNode res = sort(n1);
while (res != null) {
System.out.print(res.val + "->");
res = res.next;
}
System.out.println("null");
}
}
2、36进制加法/减法
加法
36进制由0-9,a-z,共36个字符表示。
要求按照加法规则计算出任意两个36进制正整数的和,如1b + 2x = 48 (解释:47+105=152)
要求:不允许使用先将36进制数字整体转为10进制,相加后再转回为36进制的做法
代码
public class t {
/**
* 代码与 LC 415 字符串相加 基本一致
*
* @param num1
* @param num2
* @return
*/
public String add36Strings(String num1, String num2) {
int i = num1.length() - 1, j = num2.length() - 1;
int carry = 0;
StringBuilder sb = new StringBuilder();
while (i >= 0 || j >= 0 || carry > 0) {
int x = i >= 0 ? getInt(num1.charAt(i)) : 0;
int y = j >= 0 ? getInt(num2.charAt(j)) : 0;
int sum = x + y + carry;
sb.append(getChar(sum % 36));
carry = sum / 36;
i--;
j--;
}
return sb.reverse().toString();
}
/**
* 将十进制整数转化为 36进制字符
*
* @param n
* @return
*/
public char getChar(int n) {
if (n <= 9) {
return (char) (n + '0');
} else {
return (char) (n - 10 + 'a');
}
}
/**
* 将36 进制字符转化为 10进制整数
*
* @param c
* @return
*/
public int getInt(char c) {
if (c <= '9') {
return c - '0';
} else {
return c - 'a' + 10;
}
}
public static void main(String[] args) {
t solution = new t();
String a = "1b", b = "2x", c;
c = solution.add36Strings(a, b);
System.out.println(c);
}
}
减法
36进制由0-9,a-z,共36个字符表示。
要求按照减法规则计算出任意两个36进制正整数的差,如48-2x =1b (解释:152-105=47)
要求:不允许使用先将36进制数字整体转为10进制,相减后再转回为36进制的做法
3、木头切割问题
给定长度为n的数组,每个元素代表一个木头的长度,木头可以任意截断,从这堆木头中截出至少k个相同长度为m的木块。已知k,求max(m)。
输入两行,第一行n, k,第二行为数组序列。输出最大值。
思路
开始用暴力:。大概思路就是从1遍历到木棍最长的长度,每次遍历的长度作为m,如果可以将所有木头截出来k个长度为m的木块,则更新最大值,最后输出最大值即可。
真正用的思路是:
二分。方法一在[1,max]寻找最大长度时是顺序遍历,由于其有序,我们可借助二分来快速检出结果。如果能截出来k个长度为x的木块,说明答案肯定 >= x,则接下来只需在[x,max]中找m最大满足条件的长度。反之则说明答案 < x,则在[1,x-1]中寻找结果。这样我们每次可以舍弃1/2的情况,因此使用二分的时间复杂度是O(n * log Len)
代码
public class t {
public static void main(String[] args) {
System.out.println(maxM(new int[]{4, 7, 2, 10, 5}, 5));
}
public static int maxM(int[] nums, int k) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
min = Math.min(nums[i], min);
max = Math.max(nums[i], max);
}
int lo = min, hi = max;
int ans = -1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (isFit(nums, k, mid)) {
ans = mid;
lo = mid + 1;
} else hi = mid - 1;
}
return ans;
}
private static boolean isFit(int[] nums, int k, int mid) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
count += nums[i] / mid;
}
return count >= k;
}
}
4、迷宫问题
二维矩阵,0可通行,1不可通行,输出左上角到右下角最短可达路径
代码
import java.util.*;
//输入两个整数,分别表示二位数组的行数,列数。再输入相应的数组,其中的1表示墙壁,
// 0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。
public class Main {
static List<List<Integer>> lists=new ArrayList<List<Integer>>();
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while (sc.hasNext()){
int row=sc.nextInt();
int col=sc.nextInt();
int[][] arr=new int[row][col];
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[0].length; j++) {
arr[i][j]=sc.nextInt();
}
}
int[][] hp=new int[row][col];
List<Integer> list=new ArrayList<Integer>();
helper(arr, 0, 0,list,row,col,hp);
Collections.sort(lists, new Comparator<List<Integer>>() {
public int compare(List<Integer> o1, List<Integer> o2) {
return o1.size()-o2.size();
}
});//对lists的元素也就是路径长度进行排序
list=lists.get(0);//拿出第一条路径(最短路径)
for (int i = 0; i < list.size(); i++) {
System.out.println("("+list.get(i)+","+list.get(++i)+")");
}
lists.clear();
}
}
private static void helper(int[][] arr, int i, int j,List<Integer> list,int row,int col,int[][] hp) {
if(i<0||j<0||i>=row||j>=col||hp[i][j]==1||arr[i][j]==1){}//所有非法情况
else if(i==row-1&&j==col-1){//到达终点
list.add(i);
list.add(j);
lists.add(new ArrayList<Integer>(list));//得到一条完整路径,添加
list.remove(list.size()-1);//将list的终点坐标清除,回溯法的核心就是办完事(达到目标)一定要完全还原之前的状态
list.remove(list.size()-1);//这里的list是引用传递,所以不清除的话会一直带着终点坐标
}
else{
list.add(i);
list.add(j);
hp[i][j]=1;
helper(arr, i+1, j, list, row, col,hp);
helper(arr, i, j+1, list, row, col,hp);
helper(arr, i-1, j, list, row, col,hp);
helper(arr, i, j-1, list, row, col,hp);
hp[i][j]=0;//回溯
list.remove(list.size()-1);//回溯
list.remove(list.size()-1);//回溯
}
}
}
5、求区间最小数乘区间和的最大值
给定一个数组,要求选出一个区间, 使得该区间是所有区间中经过如下计算的值最大的一个:区间中的最小数 * 区间所有数的和。
数组中的元素都是非负数。
输入两行,第一行n表示数组长度,第二行为数组序列。输出最大值。
其余描述
思路
利用单调栈寻找左右边界
把每个数字都看作是当前区间内的最小值,那么只要区间和的值越大,结果值就越大。
因此我们可以利用(递增)单调栈得到每个元素的左边界和右边界
(边界的定义即为 左/右边 第一个比该元素更小的值),最后用每个元素乘以每个元素对应的区间和,找出最大值即可。
为了防止每个元素重复计算一段区间和,可以提前计算一个前缀和数组,用于保存某元素之前的各项和(不含该元素),
求取一段区间和的时候用右边界的前缀和减去左边界的前缀和即可。
这个方法的优点在于只需要遍历数组一次,大大降低了时间复杂度.
时间复杂度为:O(2n). 各个元素进出栈各一次。
代码
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int len = sc.nextInt();
int[] nums = new int[len];
for (int i = 0; i < len; i++) {
nums[i] = sc.nextInt();
}
System.out.println(getMaxIntervalSum(nums));
}
public static long getMaxIntervalSum(int[] nums) {
long max = 0;
long[] preSum = new long[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
preSum[i] = preSum[i - 1] + nums[i - 1];
}
Stack<Integer> stack = new Stack<>();
for (int i = 0; i <= nums.length; i++) {
int curr = i == nums.length ? -1 : nums[i];
while (!stack.isEmpty() && curr < nums[stack.peek()]) {
int num = nums[stack.pop()];
if (stack.isEmpty()) {
max = Math.max(max, preSum[i] * num);
} else {
max = Math.max(max, (preSum[i] - preSum[stack.peek() + 1]) * num);
}
}
stack.push(i);
}
return max;
}
}
6、求一个数的立方根
求一个数的立方根,可能会有不同精度要求
思想
用牛顿迭代法。这个问题就可以转换为一个数的立方与数A之间的函数
通过迭代,每次产生下一个x,来逼近真正我们需要的值。注意x不能等于0.
代码
public static void help(double i) {
boolean positive = true;
// 处理正负数
if (i < 0) {
positive = false;
i = -i;
}
double x = 1;
while (Math.abs(x * x * x - i) > 0.0001) {
x = x - (x * x * x - i) / (3 * x * x);
}
if (!positive) {
x = -x;
}
System.out.println(String.format("%.1f", x));
}
7、阿拉伯数字转中文数字
输入万以下的正整数,转换成相应的大写汉字(比如:2222,为贰仟贰佰贰拾贰,注意:2000–>贰仟);
思路
1、0~9的int数组,零至玖的数组
2、取到相应下标,得到中文汉字
3、我只考虑到了2000,判断就是为零的不拼接,
注意:在当前下如果为0的时候,判断当前下一个是不是0,是的话跳过,不是的话拼接。
代码
public static void getChinese(int questNum) {
String questString = String.valueOf(questNum);
int[] num = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
String chinese[] = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
//如果超过一万,就在dw[]加个“万”
String dw[] = {"个", "拾", "百", "仟"};
/*****************************以上是一一映射*************************************/
int length = questString.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
char c = questString.charAt(i);
//如果当前为0,判断下一个是否为0,是的话跳过
if (c == '0' && i + 1 < length && questString.charAt(i + 1) == '0') {
continue;
}
int index = Integer.parseInt(c + "");
int numIndex = num[index];
String indexChinese = chinese[numIndex];
if (i == length - 1) {
if (c != '0') {
sb.append(indexChinese);
}
} else {
sb.append(indexChinese);
if (c != '0') {
int dwIndex = length - 1 - i;
sb.append(dw[dwIndex]);
}
}
}
System.out.print(sb);
}
8、双栈排序
给定一个乱序的栈,设计算法将其升序排列
思路
要排序的栈为stack,辅助的栈为help,在stack上执行pop操作,弹出的元素记为cur
1.如果cur大于或者等于help的栈顶元素,则将cur直接压入help(目的是要大的放在下面)
2.如果cur小于help的栈顶元素,则将help的元素逐一弹出,逐一压入stack,直到1。
就是为了将大的数都放在help的底下。这样出栈就是从小到大了
代码
public static ArrayList<Integer> twoStacksSort(int[] numbers) {
// write code here
Stack<Integer> stack = new Stack<Integer>();
Stack<Integer> help = new Stack<Integer>();
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < numbers.length; i++)
stack.push(numbers[i]);
while (!stack.isEmpty()) {
int cur = stack.pop();
while (!help.isEmpty() && help.peek() < cur) { //比cur小 出栈
stack.push(help.pop());
}
help.push(cur);
}
while (!help.isEmpty()) {
list.add(stack.push(help.pop()));
}
return list;
}
9、反转双向链表
代码
public class DoubleNode {
public int value;
public DoubleNode next;
public DoubleNode last;
public DoubleNode(int data) {
this.value = data;
}
}
public DoubleNode reverseList(DoubleNode head) {
DoubleNode pre = null;
DoubleNode next = null;
while (head != null) {
next = head.next;
head.next = pre;
head.last = next;
pre = head;
head = next;
}
return pre;
}
10、小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:
[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16
要求时间复杂度O(NlogN),空间复杂度O(N)
思路
其实还是用了归并排序。 整个数组的小和=数组左半部分贡献的小和+数组右半部分贡献的小和+左右两部分之间贡献的小和
整个思路大概是这样,然后具体看左右两部分之间贡献的小和
代码
public class Main{
public static int[] tmp=new int[100000];
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int N=sc.nextInt();
int[] arr=new int[N];
for(int i=0;i<N;i++){
arr[i]=sc.nextInt();
}
long count=getMinSum(arr,0,N-1);
System.out.println(count);
}
public static long merge(int[] arr,int left,int mid,int right){
int len=arr.length;
int i=left,j=mid+1;
int k=0;
long res=0;
while(i<=mid && j<=right){
if(arr[i]<=arr[j]){
res += (right-j+1)*arr[i];
tmp[k++]=arr[i++];
}else{
tmp[k++]=arr[j++];
}
}
while(i<=mid) tmp[k++]=arr[i++];
while(j<=right) tmp[k++]=arr[j++];
for(i=left,k=0;i<=right;i++){
arr[i]=tmp[k++];
}
return res;
}
public static long getMinSum(int[] arr,int left,int right){
if(left==right) return 0;
int mid=(left+right)/2;
long lSum=getMinSum(arr,left,mid);
long rSum=getMinSum(arr,mid+1,right);
if(arr[mid]<=arr[mid+1]){
long tmpSum=0;
for(int i=left;i<=mid;i++){
tmpSum += arr[i];
}
return lSum+rSum+tmpSum*(right-mid);
}
long crossSum=merge(arr,left,mid,right);
return lSum+rSum+crossSum;
}
}
11、两个有序数组的第K小数
给定两个有序数组arr1和arr2,已知两个数组的长度分别为 m1 和 m2,求两个数组中的第 K 小数。要求时间复杂度O(log(m1 + m2))。
【举例】
例如 arr1 = [1, 2,3],arr2 = [3,4,5,6],K = 4。
则第 K 小数为 3.
例如 arr1 = [0,1,2],arr2 = [3,4,5,7,8], K = 3;
则第 K 小数为 2.
思路
采用递归的方法不断缩小 K 的,把求第 K 小元素转化为第 (K-K/2) 小元素….
代码
public static int findKth(int[] arr1, int[] arr2, int k) {
if (arr1 == null || arr1.length < 1) {
return arr2[k - 1];
}
if (arr2 == null || arr2.length < 1) {
return arr1[k - 1];
}
// 注意这个函数的参数有7个,上面那个函数的参数只有3个,同名不同函数哈
return findKth(arr1, 0, arr1.length - 1, arr2, 0, arr2.length - 1, k - 1);
}
public static int findKth(int[] arr1, int l1, int r1, int[] arr2, int l2, int r2, int k) {
// 递归结束条件
if (l1 > r1) {
return arr2[l2 + k];
}
if (l2 > r2) {
return arr1[l1 + k];
}
if (k == 0)// 注意,k == 0的结束条件与上面两个结束条件不能颠倒。
{
return Math.min(arr1[l1], arr2[l2]);
}
int md1 = l1 + k / 2 < r1 ? l1 + k / 2 : r1;
int md2 = l2 + k / 2 < (r2 - l1) ? l2 + k / 2 : r2;
if (arr1[md1] < arr2[md2]) {
return findKth(arr1, md1 + 1, r1, arr2, l2, r2, k - k / 2 - 1);
} else if (arr1[md1] > arr2[md2]) {
return findKth(arr1, l1, r1, arr2, md2 + 1, r2, k - k / 2 - 1);
} else {
return arr1[md1];//返回arr2[md2]也可以,一样的。
}
}
// 测试
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = {3, 4, 5, 6};
System.out.println(findKth(arr1, arr2, 4));
}