补充题搜集

  • 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. 按奇偶位置拆分链表,得1->3->5->7->NULL和8->6->4->2->NULL
  2. 反转偶链表,得1->3->5->7->NULL和2->4->6->8->NULL
  3. 合并两个有序链表,得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,第二行为数组序列。输出最大值。

java求立方根号函数_算法

思路

开始用暴力:。大概思路就是从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表示数组长度,第二行为数组序列。输出最大值。

java求立方根号函数_List_02

其余描述

java求立方根号函数_进制_03

思路

利用单调栈寻找左右边界
把每个数字都看作是当前区间内的最小值,那么只要区间和的值越大,结果值就越大。
因此我们可以利用(递增)单调栈得到每个元素的左边界和右边界
(边界的定义即为 左/右边 第一个比该元素更小的值),最后用每个元素乘以每个元素对应的区间和,找出最大值即可。
为了防止每个元素重复计算一段区间和,可以提前计算一个前缀和数组,用于保存某元素之前的各项和(不含该元素),
求取一段区间和的时候用右边界的前缀和减去左边界的前缀和即可。
这个方法的优点在于只需要遍历数组一次,大大降低了时间复杂度.
时间复杂度为: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之间的函数

java求立方根号函数_进制_04


通过迭代,每次产生下一个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));
    }