剑指offer刷题积累(Java)

目录

入门

简单

1-替换字符串里的空格

2-返回数组中重复的数字

3-从尾到头打印链表(反转链表)

4-用两个栈实现队列

5- 打印从1到最大的n位数

6-删除链表的节点

7-链表中倒数第k个结点

8-反转链表

9-合并两个有序链表

10-二叉树的镜像

11-对称的二叉树

12-顺时针打印矩阵

13- 包含min函数的栈

14-从上往下打印二叉树

15-数组中出现次数超过一半的数字

16-连续子数组的最大和

17-第一个只出现一次的字符


入门

简单

1-替换字符串里的空格

思路:依次遍历String中的每一个字符string.charAt(i),用StringBuilder来存储最终结果,判断是否为空格,是则往stringbuilder添加要替换成的字符,否则直接添加。

2-返回数组中重复的数字

思路:用HashSet来存储数据,遍历数组将数字添加到set里,用set.contains(i)方法判断是否有重复,有则直接返回。

public int duplicate (int[] numbers) {
        // write code here
        Set<Integer> set = new HashSet<>();
        for(int i : numbers){
            if(!set.contains(i)){
                set.add(i);
            }else{
                return i;
            }
        }
        return -1;
    }

3-从尾到头打印链表(反转链表)

思路1:递归,遍历到链表尾部,在将尾部数据添加到数组或集合里

思路2:栈

4-用两个栈实现队列

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if(stack1.empty()&&stack2.empty()){
            throw new RuntimeException("Queue is empty!");
        }
        if(stack2.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

5- 打印从1到最大的n位数

思路:最大的n位数就是10的n次方减一,如1对应9,2对应99

    public int[] printNumbers (int n) {
        // write code here
        int max =(int) Math.pow(10,n)-1;
        
        int[] res = new int[max];
        for(int i = 1;i<=max;i++){
            res[i-1] = i;
        }
        return res;
    }

6-删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点

思路:迭代遍历

public class Solution {
    public ListNode deleteNode (ListNode head, int val) {
        //加入一个头节点
        ListNode res = new ListNode(0);
        res.next = head;
        //前序节点
        ListNode pre = res;
        //当前节点
        ListNode cur = head;
        //遍历链表
        while(cur != null){
            //找到目标节点
            if(cur.val == val){
                //断开连接
                pre.next = cur.next;
                break;
            }
            pre = cur;
            cur = cur.next;
        }
        //返回去掉头节点
        return res.next;
    }
}

7-链表中倒数第k个结点

思路:

  • step 1:准备一个快指针,从链表头开始,在链表上先走kkk步。
  • step 2:准备慢指针指向原始链表头,代表当前元素,则慢指针与快指针之间的距离一直都是kkk。
  • step 3:快慢指针同步移动,当快指针到达链表尾部的时候,慢指针正好到了倒数kkk个元素的位置。
public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int n = 0;
        ListNode fast = pHead;
        ListNode slow = pHead;
        //快指针先行k步
        for(int i = 0; i < k; i++){ 
            if(fast != null)
                fast = fast.next;
            //达不到k步说明链表过短,没有倒数k
            else
                return slow = null;
        }
        //快慢指针同步,快指针先到底,慢指针指向倒数第k个
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

 8-反转链表

思路1:使用栈解决

import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
    Stack<ListNode> stack= new Stack<>();
    //把链表节点全部摘掉放到栈中
    while (head != null) {
        stack.push(head);
        head = head.next;
    }
    if (stack.isEmpty())
        return null;
    ListNode node = stack.pop();
    ListNode dummy = node;
    //栈中的结点全部出栈,然后重新连成一个新的链表
    while (!stack.isEmpty()) {
        ListNode tempNode = stack.pop();
        node.next = tempNode;
        node = node.next;
    }
    //最后一个结点就是反转前的头结点,一定要让他的next
    //等于空,否则会构成环
    node.next = null;
    return dummy;
}
}

9-合并两个有序链表

思路1:递归

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null){
            return list2;
        }
        else if(list2==null){
            return list1;
        }
        if(list2.val>list1.val){
            list1.next = Merge(list1.next,list2);
            return list1;
        }
        else{
            list2.next = Merge(list1,list2.next);
            return list2;
        }
    }
}

思路2:双指针

10-二叉树的镜像

思路:递归。

因为我们需要将二叉树镜像,意味着每个左右子树都会交换位置,如果我们从上到下对遍历到的节点交换位置,但是它们后面的节点无法跟着他们一起被交换,因此我们可以考虑自底向上对每两个相对位置的节点交换位置,这样往上各个子树也会被交换位置。

自底向上的遍历方式,我们可以采用后序递归的方法。

public class Solution {
    public TreeNode Mirror (TreeNode pRoot) {
        //空树返回
        if(pRoot == null)
            return null;
        //先递归子树
        TreeNode left = Mirror(pRoot.left); 
        TreeNode right = Mirror(pRoot.right);
        //交换
        pRoot.left = right;
        pRoot.right = left;
        return pRoot;
    }
}

11-对称的二叉树

给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)

思路1:递归

public class Solution {
    boolean recursion(TreeNode root1, TreeNode root2){
        //可以两个都为空
        if(root1 == null && root2 == null)
            return true;
        //只有一个为空或者节点值不同,必定不对称
        if(root1 == null || root2 == null || root1.val != root2.val)
            return false;
        //每层对应的节点进入递归比较
        return recursion(root1.left, root2.right) && recursion(root1.right, root2.left);
    }
    boolean isSymmetrical(TreeNode pRoot) {
        return recursion(pRoot, pRoot);
    }
}

思路2:队列


public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        //空树为对称的
        if(pRoot == null) 
            return true;
        //辅助队列用于从两边层次遍历
        Queue<TreeNode> q1 = new LinkedList<TreeNode>(); 
        Queue<TreeNode> q2 = new LinkedList<TreeNode>();
        q1.offer(pRoot.left);
        q2.offer(pRoot.right);
        while(!q1.isEmpty() && !q2.isEmpty()){ 
            //分别从左边和右边弹出节点
            TreeNode left = q1.poll(); 
            TreeNode right = q2.poll();
            //都为空暂时对称
            if(left == null && right == null)
                continue;
            //某一个为空或者数字不相等则不对称
            if(left == null || right == null || left.val != right.val)
                return false;
            //从左往右加入队列
            q1.offer(left.left); 
            q1.offer(left.right);
            //从右往左加入队列
            q2.offer(right.right); 
            q2.offer(right.left);
        }
        //都检验完都是对称的
        return true;
    }
}

12-顺时针打印矩阵

思路:边界模拟法

  • step 1:首先排除特殊情况,即矩阵为空的情况。
  • step 2:设置矩阵的四个边界值,开始准备螺旋遍历矩阵,遍历的截止点是左右边界或者上下边界重合。
  • step 3:首先对最上面一排从左到右进行遍历输出,到达最右边后第一排就输出完了,上边界相应就往下一行,要判断上下边界是否相遇相交。
  • step 4:然后输出到了右边,正好就对最右边一列从上到下输出,到底后最右边一列已经输出完了,右边界就相应往左一列,要判断左右边界是否相遇相交。
  • step 5:然后对最下面一排从右到左进行遍历输出,到达最左边后最下一排就输出完了,下边界相应就往上一行,要判断上下边界是否相遇相交。
  • step 6:然后输出到了左边,正好就对最左边一列从下到上输出,到顶后最左边一列已经输出完了,左边界就相应往右一列,要判断左右边界是否相遇相交。
  • step 7:重复上述3-6步骤直到循环结束。
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
         //先排除特殊情况
        if(matrix.length == 0) {
            return res;
        }
        //左边界
        int left = 0; 
        //右边界
        int right = matrix[0].length - 1; 
        //上边界
        int up = 0; 
        //下边界
        int down = matrix.length - 1; 
        //直到边界重合
        while(left <= right && up <= down){ 
            //上边界的从左到右
            for(int i = left; i <= right; i++) 
                res.add(matrix[up][i]); 
            //上边界向下
            up++; 
            if(up > down)
                break;
            //右边界的从上到下
            for(int i = up; i <= down; i++) 
                res.add(matrix[i][right]);
            //右边界向左
            right--; 
            if(left > right)
                break;
            //下边界的从右到左
            for(int i = right; i >= left; i--) 
                res.add(matrix[down][i]);
            //下边界向上
            down--; 
            if(up > down)
                break; 
            //左边界的从下到上
            for(int i = down; i >= up; i--) 
                res.add(matrix[i][left]);
            //左边界向右
            left++; 
            if(left > right)
                break;
        }
        return res;
    }
}

13- 包含min函数的栈

思路:双栈法。一个用来存原数据,一个用来存最小值

import java.util.Stack;

public class Solution {
    //用于栈的push 与 pop
    Stack<Integer> s1 = new Stack<Integer>(); 
    //用于存储最小min
    Stack<Integer> s2 = new Stack<Integer>(); 
    public void push(int node) {
        s1.push(node);  
        //空或者新元素较小,则入栈
        if(s2.isEmpty() || s2.peek() > node)  
            s2.push(node);
        else
            //重复加入栈顶
            s2.push(s2.peek());  
    }
    
    public void pop() {
        s1.pop();
        s2.pop();
    }
    
    public int top() {
        return s1.peek();
    }
    
    public int min() {
        return s2.peek();
    }
}

14-从上往下打印二叉树

思路:层次遍历,队列

import java.util.*;
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> res = new ArrayList();
        if(root == null)
            //如果是空,则直接返回空数组
            return res; 
        //队列存储,进行层次遍历
        Queue<TreeNode> q = new ArrayDeque<TreeNode>(); 
        q.offer(root);
        while(!q.isEmpty()){
            TreeNode cur = q.poll();
            res.add(cur.val);
            //若是左右孩子存在,则存入左右孩子作为下一个层次
            if(cur.left != null)
                q.add(cur.left);
            if(cur.right != null)
                q.add(cur.right);
        }
        return res;
    }
}

15-数组中出现次数超过一半的数字

思路:哈希法

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        //哈希表统计每个数字出现的次数
        HashMap<Integer, Integer> mp = new HashMap<Integer, Integer>(); 
        //遍历数组
        for(int i = 0; i < array.length; i++){ 
            //统计频率
            if(!mp.containsKey(array[i]))
                mp.put(array[i], 1);
            else
                mp.put(array[i], mp.get(array[i]) + 1);
            //一旦有个数大于长度一半的情况即可返回
            if(mp.get(array[i]) > array.length / 2) 
                return array[i];
        }
        return 0;
    }
}

16-连续子数组的最大和

方法1:暴力法,时间复杂度O(n^2),空间复杂度O(1)

public int FindGreatestSumOfSubArray(int[] array) {
        int max = array[0];
        int sum = 0;
        for(int i=0;i<array.length;i++){
            // 每开启新的循环,需要把sum归零
            sum = 0;
            for(int j=i;j<array.length;j++){
                // 这里是求从i到j的数值和
                sum += array[j];
                // 每次比较,保存出现的最大值
                max = Math.max(max,sum);
            }
        }
        return max;
    }

方法2:动态规划,时间复杂度O(n),空间复杂度O(n)

public int FindGreatestSumOfSubArray(int[] array) {
        int[] dp = new int[array.length];
        int max = array[0];
        dp[0] = array[0];
        for(int i=1;i<array.length;i++){
            // 动态规划,状态转移方程,确定dp[i]的最大值
            dp[i] = Math.max(dp[i-1] + array[i], array[i]);
            // 每次比较,保存出现的最大值
            max = Math.max(max,dp[i]);
        }
        return max;
    }

方法3:优化动态规划,时间复杂度O(n),空间复杂度O(1)

public int FindGreatestSumOfSubArray(int[] array) {
        int sum = 0;
        int max = array[0];
        for(int i=0;i<array.length;i++){
            // 优化动态规划,确定sum的最大值
            sum = Math.max(sum + array[i], array[i]);
            // 每次比较,保存出现的最大值
            max = Math.max(max,sum);
        }
        return max;
}

17-第一个只出现一次的字符

思路:哈希表

import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        HashMap<Character, Integer> mp = new HashMap<>();
        //统计每个字符出现的次数
        for(int i = 0; i < str.length(); i++) 
            mp.put(str.charAt(i), mp.getOrDefault(str.charAt(i), 0) + 1);
        //找到第一个只出现一次的字母
        for(int i = 0; i < str.length(); i++) 
            if(mp.get(str.charAt(i)) == 1)
                return i;
        //没有找到
        return -1; 
    }
}

18-数字序列中某一位的数字

思路:

  • step 1:通过对每个区间起点数字的计算,按照上述规律求得该区间的位数,n不断减去它前面区间的位数,定位到属于它的区间。
  • step 2:通过除以位数定位n在哪个数字上,用字符串形式表示。
  • step 3:通过在字符串上位置对几位数取模定位目标数字。
import java.util.*;
public class Solution {
    public int findNthDigit (int n) {
        //记录n是几位数
        int digit = 1;
        //记录当前位数区间的起始数字:1,10,100...
        long start = 1; 
        //记录当前区间之前总共有多少位数字
        long sum = 9; 
        //将n定位在某个位数的区间中
        while(n > sum){
            n -= sum;
            start *= 10; 
            digit++; 
            //该区间的总共位数
            sum = 9 * start * digit;
        }
        //定位n在哪个数字上
        String num = "" + (start + (n - 1) / digit);
        //定位n在数字的哪一位上
        int index = (n - 1) % digit;
        return (int)(num.charAt(index)) - (int)('0');
    }
}

 19-两个链表的第一个公共结点

思路1:两层枚举,逐个检查哪个节点相同

public class Solution {
    public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
        for (ListNode h1 = a; h1 != null ; h1 = h1.next) {
            for (ListNode h2 = b; h2 != null ; h2 = h2.next) {
                if (h1 == h2) return h1;
            }
        }
        return null;
    }
}

思路二:

使用 Set 数据结构,先对某一条链表进行遍历,同时记录下来所有的节点。

然后在对第二链条进行遍历时,检查当前节点是否在 Set 中出现过,第一个在 Set 出现过的节点即是交点。

import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
        Set<ListNode> set = new HashSet<>();
        while (a != null) {
            set.add(a);
            a = a.next;
        }
        while (b != null && !set.contains(b)) b = b.next;
        return b;
    }
}

思路三:遍历两个链表,找出较长的那个链表,忽略较长链表的头部多出的一截(多出的一截不可能存在公共节点)然后依次对比节点是否相等,返回第一个节点相等的指针即可。空间复杂度O(1)事件复杂度O(m+n)(两个链表都进行了遍历,找出长度)

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {   
        int len1 = 0;
        int len2 = 0;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=null){len1++;p1=p1.next;}
        while(p2!=null){len2++;p2=p2.next;}
        ListNode maxlist = len1>len2?pHead1:pHead2;
        ListNode minlist = len1>len2?pHead2:pHead1;
        int count =0;
        while(maxlist!=minlist){
            count++;
            if(count>Math.abs(len1-len2)){
                minlist = minlist.next;
            }
            maxlist = maxlist.next;
        }
        return maxlist;
    }
}

20-数字在升序数组中出现的次数

思路一:暴力

import java.util.*;
public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int n = 0;
        for(int i = 0;i<array.length;i++){
            if(array[i]==k)
                n++;
            if(array[i]>k)
                break;
        }
        return n;
       
    }
}

思路2:二分

  • step 1:写一个二分查找的函数在数组中找到某个元素出现的位置。每次检查区间中点值,根据与中点的大小比较,确定下一次的区间。
  • step 2:分别使用二分查找,找到k+0.5和k-0.5应该出现的位置,中间的部分就全是k,相减计算次数就可以了。
public class Solution {
    //二分查找
    private int bisearch(int[] data, double k){ 
        int left = 0;
        int right = data.length - 1;
        //二分左右界
        while(left <= right){ 
            int mid = (left + right) / 2;
            if(data[mid] < k)
                left = mid + 1;
            else if(data[mid] > k)
                right = mid - 1;
        }
        return left;
    }
    public int GetNumberOfK(int [] array , int k) {
        //分别查找k+0.5和k-0.5应该出现的位置,中间的部分就全是k
        return bisearch(array, k + 0.5) - bisearch(array, k - 0.5);
    }
}

 21-二叉树的深度

思路1:递归


import java.lang.Math;
public class Solution {
	public int TreeDepth(TreeNode pRoot)
    {
    	if(pRoot == null){
            return 0;
        }
        int left = TreeDepth(pRoot.left);
        int right = TreeDepth(pRoot.right);
        return Math.max(left, right) + 1;
    }
}

思路2:层次遍历

22-扑克牌顺子

思路:将 nums 数组依次装入 set集合,遇到 0 则返回装下一个元素,出现重复元素则返回 false,并在其中记录max,min,最终max-min >= 5的都不是顺子;

import java.util.*;
public class Solution {
    public boolean IsContinuous(int [] numbers) {
        Set<Integer> set = new HashSet<>();
        int max = Integer.MIN_VALUE, min =Integer.MAX_VALUE;
        //遍历数组
        for (int number:
             numbers) {
            if(number == 0) {
                continue;
            }
            //包含相同牌则直接返回,否则加入
            if(set.contains(number)){
                return false;
            }else {
                set.add(number);
            }
            //每次遍历记录最大值,最小值
            max = StrictMath.max(max,number);
            min = StrictMath.min(min,number);
        }
        return max - min < 5;

    }
}
import java.util.*;
public class Solution {
    public boolean IsContinuous(int [] numbers) {
        int king = 0,max,min;
        //将数组排序
        Arrays.sort(numbers);
        for(int i = 0; i < 4; i++) {
            //记录王牌个数
            if(numbers[i] == 0) king++;
            else if(numbers[i] == numbers[i + 1]) return false;
        }
        max = numbers[4];
        min = numbers[king];// king的个数会占去前置位的数组,nums[king]必然是最小值
        //最大值和最小值进行比较小于5即是顺子
        return  numbers[4] - numbers[king] < 5;
    }
}

23-

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值