算法学习-剑指 Offer(第 2 版),虚空剑指冠军刷题家(持续更新中)

程序员面试的经典题库了,记录一下自己的java题解以及一些心得体会,方便自己复习。
参考:

力扣-剑指Offer(第二版)

与我的另一篇文章,剑指 Offer(专项突击版)同步更新中。

相关题目

03.数组中重复的数字
class Solution {
    public int findRepeatNumber(int[] nums) {
        HashSet<Integer> set=new HashSet<>();
        for(int i:nums){
            if(set.contains(i)) return i;
            else set.add(i);
        }
        return 0;
    }
}
04.二维数组中的查找

这题关键是能够将矩阵想象成二叉搜索树的形式,通过逆时针旋转45度,在搜寻的过程中可以根据当前值和target的关系决定方向。

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        int n=matrix.length;
        if(n==0) return false;
        int m=matrix[0].length;
        int i=m-1;
        int j=0;
        while(i>=0&&j<=n-1){
            if(matrix[j][i]>target) i--;
            else if(matrix[j][i]<target) j++;
            else return true;
        }
        return false;
    }
}
05.替换空格
class Solution {
    public String replaceSpace(String s) {
        StringBuilder sb=new StringBuilder();
        int len=s.length();
        for(int i=0;i<len;i++){
            char c=s.charAt(i);
            if(c==' ') sb.append("%20");
            else sb.append(c);
        }
        return sb.toString();
    }
}
06.从尾到头打印链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        int index=0;
        int[] temp=new int[10000];
        ListNode cur=head;
        while(cur!=null){
            temp[index++]=cur.val;
            cur=cur.next;
        }
        int[]ans=new int[index];
        int idx=0;
        for(int j=index-1;j>=0;j--){
            ans[idx++]=temp[j];
        }
        return ans;
    }
}
07.重建二叉树

递归重建,两个数组区间的划分很重要

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int len1=preorder.length;
        int len2=inorder.length;
        return buildHelper(preorder,inorder,0,len1-1,0,len2-1);
    }
    public TreeNode buildHelper(int[]preorder,int[]inorder,int prea,int preb,int ina,int inb){
        if(prea>preb) return null;
        if(prea==preb) return new TreeNode(preorder[prea]);
        int target=preorder[prea];
        int pivotidx=0;
        for(int i=ina;i<=inb;i++){
            if(inorder[i]==target){
                pivotidx=i;
                break;
            }
        }
        TreeNode root=new TreeNode(target);
        root.left=buildHelper(preorder,inorder,prea+1,prea+1+pivotidx-1-ina,ina,pivotidx-1);
        root.right=buildHelper(preorder,inorder,prea+1+pivotidx-1-ina+1,preb,pivotidx+1,inb);
        return root;
    }
}
09.两个栈实现队列

关键是掌握好两个栈进栈出栈的搭配时机,先往一个栈a进,代表队列尾部插入;如果要队列头部删除,如果另一个栈b为空,则将栈a的数据全部送入栈b,然后出栈,如果栈b不为空,直接出栈。

class CQueue {
    Stack<Integer> sta;
    Stack<Integer> stb;
    public CQueue() {
        sta=new Stack<>();
        stb=new Stack<>();
    }
    
    public void appendTail(int value) {
        sta.push(value);
    }
    
    public int deleteHead() {
        if(stb.isEmpty()){
            while(!sta.isEmpty()){
                stb.push(sta.pop());
            }
        }
        return stb.isEmpty()?-1:stb.pop();
    }
}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */
10.斐波那契数列
class Solution {
    public int fib(int n) {
        int[]dp=new int[100+1];
        int mod=(int)1e9+7;
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=(dp[i-1]+dp[i-2])%mod;
        }
        return dp[n];
    }
}
10.青蛙跳台阶问题
class Solution {
    public int numWays(int n) {
        int mod=(int)1e9+7;
        int[]dp=new int[101];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=(dp[i-1]+dp[i-2])%mod;
        }
        return dp[n];
    }
}
11.旋转数组最小的数字

利用二段性进行二分查找,前一半元素>=nums[0],后一半元素<nums[0],但是考虑到原数组可能在重复点旋转了,需要消除后半段和nums]0]相同的那部分。

class Solution {
    public int minArray(int[] nums) {
        int len=nums.length;
        int left=0;
        int right=len-1;
        //消除重复性
        while(right>0&&nums[right]==nums[0]) right--;
        while(left<right){
            int mid=(left+right+1)/2;
            //找到>=nums[0]的最后一个元素
            if(nums[mid]>=nums[0]){
                left=mid;
            }else{
                right=mid-1;
            }
        }
        return left+1<=len-1?nums[left+1]:nums[0];
    }
}
12.矩阵中的路径

dfs+回溯,常规做法,vis访问矩阵记录信息防止回环

class Solution {
    int m,n;
    public boolean exist(char[][] board, String word) {
        m=board.length;
        n=board[0].length;
        boolean[][]vis=new boolean[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(dfs(board,vis,word,i,j,0)) return true;
            }
        }
        return false;
    }

    //表示[i,j]出发后续能够构成单词word
    public boolean dfs(char[][] board, boolean[][]vis,String word,int i,int j,int depth){
        //需要先进行真的返回,不然可能在只有一个元素的时候,优先返回的是i,j越界,而不是检索到最后一个位置
        //本来没写真的条件返回,if判断里永远都是false
        if(depth>=word.length()) return true;
        if(i<0||i>=m||j<0||j>=n) return false;
        if(board[i][j]!=word.charAt(depth)||vis[i][j]) return false;
        vis[i][j]=true;
        int[]dx=new int[]{0,-1,0,1};
        int[]dy=new int[]{-1,0,1,0};
        for(int k=0;k<4;k++){
            if(dfs(board,vis,word,i+dx[k],j+dy[k],depth+1)){
                return true;
            } 
        }
        //四周检查完不满足的话需要回溯vis
        vis[i][j]=false;
        return false;
    }
}

上面对于dfs base情况的分析不够简洁,所以true一定要在前面,下面把所有false写在一块,所以true在false的后面。

class Solution {
    int m,n;
    public boolean exist(char[][] board, String word) {
        m=board.length;
        n=board[0].length;
        boolean[][]vis=new boolean[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(dfs(board,vis,word,i,j,0)) return true;
            }
        }
        return false;
    }

    //表示[i,j]出发后续能够构成单词word
    public boolean dfs(char[][] board, boolean[][]vis,String word,int i,int j,int depth){
        if(i<0||i>=m||j<0||j>=n||board[i][j]!=word.charAt(depth)||vis[i][j]) return false;
        if(depth==word.length()-1) return true;
        vis[i][j]=true;
        int[]dx=new int[]{0,-1,0,1};
        int[]dy=new int[]{-1,0,1,0};
        for(int k=0;k<4;k++){
            if(dfs(board,vis,word,i+dx[k],j+dy[k],depth+1)){
                return true;
            } 
        }
        //四周检查完不满足的话需要回溯vis
        vis[i][j]=false;
        return false;
    }
}

下面这种做法就省略了vis数组,直接在board上做标记,也可以通过下标进行回溯。

class Solution {
    int m,n;
    public boolean exist(char[][] board, String word) {
        m=board.length;
        n=board[0].length;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(dfs(board,word,i,j,0)) return true;
            }
        }
        return false;
    }

    //表示[i,j]出发后续能够构成单词word
    public boolean dfs(char[][] board,String word,int i,int j,int depth){
        if(i<0||i>=m||j<0||j>=n||board[i][j]!=word.charAt(depth)) return false;
        if(depth==word.length()-1) return true;
        board[i][j]=' ';
        int[]dx=new int[]{0,-1,0,1};
        int[]dy=new int[]{-1,0,1,0};
        for(int k=0;k<4;k++){
            if(dfs(board,word,i+dx[k],j+dy[k],depth+1)){
                return true;
            } 
        }
        //四周检查完不满足的话需要回溯vis
        board[i][j]=word.charAt(depth);
        return false;
    }
}
48. 最长不含重复字符的子字符串
class Solution {
    public int lengthOfLongestSubstring(String s) {
        HashSet<Character> win=new HashSet<>();
        int left=0;
        int ans=0;
        for(int right=0;right<s.length();right++){
            char c=s.charAt(right);
            while(win.contains(c)){
                win.remove(s.charAt(left++));
            }
            win.add(c);
            ans=Math.max(ans,right-left+1);
        }
        return ans;
    }
}
55. 二叉树深度
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        return dfs(root);
    }

    public int dfs(TreeNode root){
        if(root==null) return 0;
        return 1+Math.max(dfs(root.left),dfs(root.right));
    }
}
55.2 平衡二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root) {
        int ans=dfs(root);
        return ans==-1?false:true;

    }

    // 递归函数两个功能:如果左右差>1,就返回-1,否则返回一边树的最大高度
    public int dfs(TreeNode root){
        if(root==null) return 0;
        int left=dfs(root.left);
        if(left==-1) return -1;
        int right=dfs(root.right);
        if(right==-1) return -1;
        int cur=Math.abs(left-right);
        if(cur>1) return -1;
        return 1+Math.max(left,right);
    }
}
62. 圆圈中最后剩下的数字

参考 题解,f(N,M)表示,N个人报数,每报到M时杀掉那个人,最终胜利者的编号。 f ( N , M ) = ( f ( N − 1 , M ) + M ) m o d    N f(N,M)=(f(N-1,M)+M) \mod N f(N,M)=(f(N1,M)+M)modN,理解这个递推式的核心在于关注胜利者的下标位置是怎么变的。每杀掉一个人,其实就是把这个数组向前移动了M位。然后逆过来,就可以得到这个递推式。我们知道f(1,M)为0;

class Solution {
    public int lastRemaining(int n, int m) {
        int ans=0;
        for(int i=2;i<=n;i++){
            ans=(ans+m)%i;
        }
        return ans;
    }
}
67.把字符串转换为整数

纯模拟,把情况考虑全,首尾去空格,首个字符是否为正负号,绝对值计算时避免long越界提前退出

class Solution {
    public int strToInt(String str) {
        //判空以及判长度为0
        if(str==null) return 0;
        String s=str.trim();
        int len=s.length();
        if(len==0) return 0;

        //判断首位
        char firstc=s.charAt(0);
        if(firstc!='-'&&firstc!='+'&&!Character.isDigit(firstc)) return 0;
        int flag=1;
        if(firstc=='-'){
            flag=-1;
            s=s.substring(1,s.length());
        }
        if(firstc=='+'){
            s=s.substring(1,s.length());
        }

        //进行绝对值计算
        len=s.length();
        long sum=0L;
        for(int i=0;i<len&&Character.isDigit(s.charAt(i));i++){
            char c=s.charAt(i);
            sum=sum*10+c-'0';
            //及时判断很重要,会出现大于long long的结果
            if(sum>Integer.MAX_VALUE) return flag==-1?Integer.MIN_VALUE:Integer.MAX_VALUE;
        }
        sum*=flag;
        return (int)sum;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网民工蒋大钊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值