【剑指offer第二版】JAVA刷题总结-ch6

53. 在排序数组中查找数字(看到单调递增或者递减,就应该想到二分查找)

53-1 数字排排序数组中出现的次数

思路:(1)暴力O(n);(2)直接二分O(n):查找第一个k和倒数第一个k的位置。k可能出现n次,时间复杂度还是O(n);(3)特别的二分O(logn)

class Solution {
    public int getNumberOfK(int[] nums, int k) {
        if(nums==null|| nums.length==0) return 0;//长度为0
        int first = getFirst(nums, 0, nums.length-1, k);//第一个
        int last = getLast(nums, 0, nums.length-1, k);//最后一个
        if(first>-1&&last>-1)
            return last-first+1;//存在
        return 0;
    }
    public int getFirst(int[] nums, int left, int right, int k){
        if(left>right) return -1;
        int m = (left+right)>>1;
        if(nums[m]>k) right = m-1;//中间的数不是第一个
        else if(nums[m]<k) left = m+1;
        else{
            if(m==0||(m>0 && nums[m-1]!=k)){//到头了或者中间的数的前一个不等于中间的数
                return m;
            }
            else right = m-1;//等于
        }
        return getFirst(nums, left, right, k);
    }
    public int getLast(int[] nums, int left, int right, int k){
        if(left>right) return -1;
        int  m = (left+right)>>1;
        if(nums[m]>k) right = m-1;
        else if(nums[m]<k) left = m+1;
        else{
            if(m==nums.length-1 || (m<nums.length-1 && nums[m+1]!=k)){
                return m;
            }
            else left = m+1;
        }
        return getLast(nums, left, right, k);
    }
}

53-2 0-n-1中缺失的数字

思路:(1)先计算总和,再计算数组中数字的总和,再相减,复杂度O(n);(2)转化为:找出数组中第一个下标与对应值不同的元素的下标。(蕴含着该数前一个等于前一个的下标)

class Solution {
    public int getMissingNumber(int[] nums) {
        if(nums==null || nums.length==0) return 0;
        int left=0, right = nums.length-1;
        if(nums[right]==right) return right+1;//如果缺失的是最后一个数字
        while(left<=right){
            if(left<0 || right>nums.length-1) return -1;//结束条件
            int mid = (left+right)>>1;
            if(nums[mid]==mid){
                left = mid+1;
            }
            else{
                if(mid==0 || nums[mid-1]==mid-1){//第一个或者前一个相等,返回当下这个
                    return mid;
                }
                right = mid-1;
            }
        }
        return -1;
    }
}

53-3 数组中数值和下标相等的元素

class Solution {
    public int getNumberSameAsIndex(int[] nums) {
        if(nums==null || nums.length==0) return -1;
        if(nums[nums.length-1] < nums.length-1) return -1;
        int left = 0, right = nums.length-1;
        while(left<=right){
            int mid = (left+right)>>1;
            if(mid==nums[mid]) return mid;
            else if(nums[mid]<mid) left=mid+1;
            else right = mid-1;
        }
        return -1;
    }
}

54. 二叉搜索树的第k大个节点

思路:中序遍历

class Solution {
    int count=0;
    TreeNode ans=null;
    public TreeNode kthNode(TreeNode root, int k) {
        countK(root, k);
        return ans;
    }
    public void countK(TreeNode root, int k){
        if(root==null||count>=k){
            return;
        }
        countK(root.left,k);
        count++;
        if(count==k){
            ans=root;
        }
        countK(root.right,k);
    }
}

55. 二叉树的深度

55-1 二叉树的深度

思路:如果一棵树只有一个节点那么它的深度是1;如果有子树,应该是左子树和右子树中,深度最大的子树的深度+1就是,整棵树的深度。时间复杂度O(n)。

class Solution {
    int count=0;
    public int treeDepth(TreeNode root) {
        if(root==null) return 0;
        else{
            return 1+Math.max(treeDepth(root.left), treeDepth(root.right));
        }
    }
}

55-2 平衡二叉树

思路:每个节点的左右子树的深度相差都不超过一。还有一种方法是后序遍历,再看到的时候再写吧。

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root==null) return true;
        int left = TreeDepth(root.left);
        int right = TreeDepth(root.right);
        int diff = right-left;
        if(diff>1 || diff<-1) return false;
        return isBalanced(root.left)&&isBalanced(root.right);//左右子树深度相差不超过一
    }
    public int TreeDepth(TreeNode root){//
        if(root==null) return 0;
        int left = TreeDepth(root.left);
        int right = TreeDepth(root.right);
        return (left>right)?(left+1):(right+1);
    }
}

56. 数组中数字出现的次数

56-1 数组中只出现一次的两个数字

思路:(1)先考虑找出数组中子会出现一次的一个数字。考虑到每个数字与自己做异或运算都等于0。所以整个数组一一进行异或,得到得就是这个数字。(2)下面是这个题的思路:(异或运算) O(n)
1. 首先遍历整个数组,对整个数组的每个值求异或。得到的结果是只出现一次的两个数的异或结果,因为这两个值是不相同的,所以异或的结果不可能为零,所以一定有一位是1,也就是一定有一位这两个值不相同。那么按这一位,将整个数组分成两部分,然后对两个数组分别进行异或遍历,就可以得到只出现一次的两个值。找到两个数不同的那一位:找到倒数第一位为1 的位置,return。

class Solution {
    public int[] findNumsAppearOnce(int[] nums) {
        int res=0;
        for(int i=0; i<nums.length; i++){
            res = res^nums[i];
        }
        int index = getFirstBit(res);//倒数第一个不同的位置
        int num1=0, num2=0;
        for(int i=0; i<nums.length; i++){//分组,这位为1为一组,为0为第二组
            if(((nums[i]>>(index-1))&1)==1) num1 = num1^nums[i];
            else num2 = num2^nums[i];
        }
        return new int[]{num1, num2};
    }
    public int getFirstBit(int res){
        int place=1;
        while(res!=0 && (res&1)!=1){
            res = res>>1;
            place++;

        }
        return place;
    }
}

56-2 数组中唯一只出现一次的数字

思路:和上一题一样,往异或方向上想没思路。这题和异就没关系了,int一共是32位,新建一个各位和的数组,我们把每个数字对应的1和0加到这个数组的对应位置上,比如1,1,1相加就变成了003,再加上2,2,2就是033,再加上4,4,4就是333,再加上1,就是334,加完后,如果某一位上是3的倍数,说明目标数字这一位上是0,如果不是3的倍数,那么这一位上就是1
时间是0(n),第二层的循环因为是常数32,所以是O(1),空间的话开了常数长度的数组也是0(1)

class Solution {
    public int findNumberAppearingOnce(int[] nums) {
        int[] sum = new int[32];
        for(int i=0; i<nums.length; i++){
            for(int j=0; j<32; j++){
                sum[j] += (nums[i]>>j) &1;
            }
        }
        int res=0;
        for(int i=0;i<32;i++){
            res+=(sum[i]%3==0)?0:1<<i;
        }
        return res;
    }
}

57. 和为s的数字

57-1 和为s的两个数字

思路:(1)暴力O(n²);(2)先排序,在用双指针,一个从前一个从后。排序复杂度O(nlogn),循环复杂度O(n)。

class Solution {
    public int[] findNumbersWithSum(int[] nums, int target) {
        if(nums==null||nums.length==1) return null;
        Arrays.sort(nums);
        int left=0, right=nums.length-1;
        int num1=0,num2=0;
        boolean find = false;
        while(left<=right){
            if(nums[left]+nums[right]==target){
                num1 = nums[left];
                num2 = nums[right];
                find = true;
                break;
            }
            else if(nums[left]+nums[right]<target) left+=1;
            else right-=1;
        }
        if(find) return new int[]{num1, num2} ;
        else return null;
    }
}

57-2 和为s的连续正数序列

思路:(双指针) O(n),大小指针分别指向2,1。当大小指针中间的数字序列之和大于给定的值,小指针向前移动
如果小于给定的值,大指针向后移动。当小指针大于给定值的二分之一,后面的值相加一定会大于给定的值,所以,循环结束条件就是当小指针小于给定值的一半。

class Solution {
    public List<List<Integer> > findContinuousSequence(int sum) {
        List<List<Integer>> ans = new ArrayList<>();
        int small = 1, big=2;
        int curSum = small+big;
        while(small<(sum>>1)+1){
            if(curSum == sum){
                List<Integer> tmp = new ArrayList<>();
                for(int i=small; i<=big; i++){
                    tmp.add(i);
                }
                ans.add(tmp);
                big++;
                curSum+=big;
            }
            else if(curSum < sum){
                big++;
                curSum += big;
            }
            else{
                curSum -=  small;
                small++;
            }
        }
        return ans;
    }
}

58. 翻转字符串

58-1 翻转单词序列

思路:先全部翻转。再碰到空格翻转前面的单词。

class Solution {
    public String reverseWords(String s) {
        if(s.length()==0) return s;
        char[] allchar = s.toCharArray();//注意函数调用
        int start = 0, end = allchar.length-1;
        allchar = reverse(allchar, start, end);
        start = 0;
        end = 0;
        while(end<allchar.length){
            if(allchar[end]==' '){
                allchar = reverse(allchar, start, end-1);
                start = end+1;
                end = end+1;
            }
            else if(end==allchar.length-1){
                allchar = reverse(allchar, start, end);
                break;
            }
            else{
                end++;
            }
        }
        return new String(allchar);//注意char[]转string

    }
    public char[] reverse(char[] allchar, int start, int end){
        for(int i=0; i<=(end-start)>>1; i++){
            char tmp = allchar[start+i];
            allchar[start+i] = allchar[end-i];
            allchar[end-i] = tmp;
        }
        return allchar;
    }
}

58-2 左旋转字符串

思路:abcdef;n=2;翻转三次(1)bacdef(2)bafedc(3)cdefab

class Solution {
    public String leftRotateString(String str,int n) {
        if(str.length()==0) return str;
        char[] allchar = str.toCharArray();
        allchar = reverse(allchar, 0, n-1);
        allchar = reverse(allchar, n, str.length()-1);
        allchar = reverse(allchar, 0, str.length()-1);
        return new String(allchar);
        
    }
    public char[] reverse(char[] charall, int start, int end){
        for(int i=0; i<=(end-start)>>1; i++){
            char tmp = charall[start+i];
            charall[start+i] = charall[end-i];
            charall[end-i] = tmp;
        }
        return charall;
    }
}

59. 队列的最大值

59-1 滑动窗口的最大值

思路:单调递减队列,时间复杂度O(n)。我们维护一个双向单调队列,队列放的是元素的下标。我们假设该双端队列的队头是整个队列的最大元素所在下标,至队尾下标代表的元素值依次降低。初始时单调队列为空。随着对数组的遍历过程中,每次插入元素前,首先需要看队头是否还能留在队列中,如果队头下标距离i超过了k,则应该出队。同时需要维护队列的单调性,如果nums[i]大于或等于队尾元素下标所对应的值,则当前队尾再也不可能充当某个滑动窗口的最大值了,故需要队尾出队。始终保持队中元素从队头到队尾单调递减。依次遍历一遍数组,每次队头就是每个滑动窗口的最大值所在下标。

时间复杂度分析:每个元素最多入队出队一次,复杂度为O(n)

class Solution {
    public int[] maxInWindows(int[] nums, int k) {
        int[] ans = new int[nums.length-k+1];
        ArrayDeque<Integer> q= new ArrayDeque<>();//队列里存储下标
        int index = 0;
        for(int i=0; i<nums.length; i++){
            while(!q.isEmpty() && (i-q.peekFirst()>=k)) {//判断队头是否需要出队
                q.pollFirst();
            }
            while(!q.isEmpty() && nums[i]>=nums[q.peekLast()]){//维护队列单调性
                q.pollLast();
            }
            q.add(i);
            if(i>=k-1){
                ans[index++] = nums[q.peekFirst()];//取队头作为窗口最大元素
            }
        }
        return ans;
    }
}

59-2 队列的最大值

思路:

60. n个骰子的点数

思路:动态规划O(n²);数组dp[i][j]表示用i个骰子扔出和为j的可能数,因为第i个骰子可能扔出1-6的点数,则dp[i][j]=dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]+dp[i-1][j-5]+dp[i-1][j-6].

class Solution {
    public int[] numberOfDice(int n) {
        int[][] dp = new int[n+1][6*n+1];//数组存储第i个骰子,和为j的个数
        for(int i=1; i<=6;i++){
            dp[1][i]=1;
        }
        for(int i=2; i<=n; i++){
            for(int j=i; j<6*n+1 ;j++){
                for(int k=1;k<=6&&j-k>=0;k++){
                    dp[i][j] += dp[i-1][j-k]; 
                }
            }
        }
        int[] ans = new int[5*n+1];
        for(int i=n,j=0;i<6*n+1;i++,j++){
            ans[j] = dp[n][i];
        }
        return ans;
    }
}

61. 扑克牌中的顺子

class Solution {
    public boolean isContinuous(int [] numbers) {
        Arrays.sort(numbers);//先排序
        int nums0 = 0;
        for(int i=0; i<numbers.length; i++){
            if(numbers[i]==0) nums0++;
            else break;
        }
        for(int i =nums0; i<numbers.length-1; i++){
            if(numbers[i]==numbers[i+1]) return false;
            else{
                nums0 = nums0-numbers[i+1]+numbers[i]+1;
            }
        }
        return nums0>=0;
    }
}

62. 圆圈中最后剩下的数(约瑟夫环)

思路:(1)遍历;时间复杂度O(mn);空间复杂度O(n);(2)递推(寻找相邻两项之间的关系):约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。时间复杂度O(n),空间复杂度O(1)。

class Solution {
    public int lastRemaining(int n, int m) {
        List<Integer> nums = new LinkedList<>();
        for(int i=0; i<n; i++){
            nums.add(i);
        }
        int index=0;
        while(nums.size()>1){
            for(int i=1;i<m;i++){
                index = (index+1)%nums.size();
            }
            nums.remove(index);
        }
        return nums.get(0);
    }
}
class Solution {
    public int lastRemaining(int n, int m) {
        if(n==1) return 0;
        return (lastRemaining(n-1,m)+m)%n;
    }
}

63. 股票的最大利润

思路:记录min和maxDiff。时间复杂度O(N)

class Solution {
    public int maxDiff(int[] nums) {
        if(nums==null || nums.length==0 || nums.length==1) return 0;
        int min = nums[0];
        int maxDiff = nums[1]-min;
        for(int i=2; i<nums.length; i++){
            if(nums[i-1]<min) min = nums[i-1];
            int curDiff = nums[i]-min;
            if(curDiff>maxDiff) maxDiff = curDiff;
        }
        return maxDiff<0?0:maxDiff;
    }
}

64.求1+2+3+4+。。。+n

思路:短路运算符

class Solution {
    public int getSum(int n) {
        int ans=n;
        boolean b = (n>0)&&(ans += getSum(n-1))>0;
        return ans;
    }
}

65. 不用加减乘除做加法

思路:位运算

class Solution {
    public int add(int num1, int num2) {
        int sum;
        int carry;
        do{
            sum = num1^num2;//异或
            carry = (num1&num2)<<1;//与运算,左移表示进位
            num1 = sum;
            num2 = carry;
            
        }while(carry!=0);
        return num1;
    }
}

66. 构建乘积数组

思路:用一个数组记录A数组中每个位置前面值的乘积,用第二个数组记录A数组中每个位置后面的乘积,然后把每个位置前后乘积相乘即可。时间复杂度O(n)。

class Solution {
    public int[] multiply(int[] A) {
        if(A.length==0 || A==null) return A;
        int[] B = new int[A.length];
        B[0] = 1;
        for(int i=1; i<A.length; i++){
            B[i] = B[i-1]*A[i-1];
        }
        int last = 1;
        for(int j=A.length-2; j>=0; j--){
            last = last * A[j+1]; 
            B[j] = B[j]*last;
        }
        return B;
    }
}
发布了16 篇原创文章 · 获赞 0 · 访问量 3119

猜你喜欢

转载自blog.csdn.net/Calliope1997/article/details/104291417