剑指offer [1- 50 ]-- java题解

剑指offer [1- 50 ]-- java题解

刷题地址

https://www.nowcoder.com/exam/oj/ta?tpId=13

1 数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
时间复杂度:O(n)
空间复杂度:O(1)

解析

既然数组长度为n只包含了0到n−1的数字,那么如果数字没有重复,这些数字排序后将会与其下标一一对应。那我们就可以考虑遍历数组,每次检查数字与下标是不是一致的,一致的说明它在属于它的位置上,不一致我们就将其交换到该数字作为下标的位置上,如果交换过程中,那个位置已经出现了等于它下标的数字,那肯定就重复了。

代码

public class Solution {
    public int duplicate (int[] numbers) {
        // write code here
        HashSet<Integer> set = new HashSet<>();
        for(int i = 0; i < numbers.length; i++){
            if(!set.add(numbers[i])){
                return numbers[i];
            }
        }
        return -1;
    }
}

2 二维数组中的查找

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。

给定 target = 3,返回 false。

解析

首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中,于是可以利用分治思维来做。

具体做法:

  • step 1:首先获取矩阵的两个边长,判断特殊情况。
  • step 2:首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。
  • step 3:若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。

代码

public class Solution {
    public boolean Find(int target, int [][] array) {
        int r = array.length - 1;
        int c = 0;
        while(r >= 0 && c < array[0].length){
            int cmp = array[r][c];
            if(cmp == target) return true;
            else if(cmp > target) r--;
            else c++;
        }
        return false;
    }
}

3 替换空格

请实现一个函数,将一个字符串s中的每个空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

代码

public class Solution {
    public String replaceSpace (String s) {
        StringBuilder sb = new StringBuilder();
        char[] cs = s.toCharArray();
        for(char c : cs){
            if(c == ' '){
                sb.append("%20");
            }else{
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

4 从尾到头打印链表

输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。

解析

用栈实现,因为栈是先进后出的,符合逆序的特点

代码

public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        LinkedList<Integer> stack = new LinkedList<>();//栈
        while(listNode != null){
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while(!stack.isEmpty()){
            res.add(stack.pop());
        }
        return res;
    }
}

5 重建二叉树

给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

解析

二叉树的根节点就是前序序列的第一个节点,这样就可以把中序序列拆分成两部分,根节点前面就是左子树,后面就是右子树,那么就知道了左右子树的节点数量,依此就可以对前序序列也进行拆分,这样左右子树的前序、中序序列都知道了,递归构建左右子树就可以了。

代码

public class Solution {
    HashMap<Integer, Integer> map = new HashMap<>();

    public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
        for (int i = 0; i < vin.length; i++) {
            map.put(vin[i], i);
        }
        TreeNode node = dfs(pre, 0, pre.length - 1, vin, 0, vin.length - 1);
        return node;
    }

    private TreeNode dfs(int[] pre, int pl, int pr, int[] vin, int vl, int vr) {
        if (pl > pr || vl > vr) return null;
        int k = map.get(pre[pl]) - vl;
        TreeNode treeNode = new TreeNode(pre[pl]);
        treeNode.left = dfs(pre, pl + 1, pl + k, vin, vl, vl + k - 1);
        treeNode.right = dfs(pre, pl + 1 + k, pr, vin, vl + k + 1, vr);
        return treeNode;
    }
}

6 二叉树的下一个结点

给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。下图为一棵有9个节点的二叉树。树中从父节点指向子节点的指针用实线表示,从子节点指向父节点的用虚线表示

代码

import java.util.*;

public class Solution {
    ArrayList<TreeLinkNode> list = new ArrayList<>();
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        if (pNode == null) return null;
        TreeLinkNode cur = pNode;
        //找到根节点
        while (pNode.next != null) {
            pNode = pNode.next;
        }
        //中序遍历,添加node
        Inorder(pNode);
        //返回下一个节点
        for (int i = 0; i < list.size() - 1; i++) {
            TreeLinkNode temp = list.get(i);
            if(cur == temp){
                return list.get(i + 1);
            }
        }
        return null;
    }

    private void Inorder(TreeLinkNode node) {
        if (node == null) return;
        Inorder(node.left);
        list.add(node);
        Inorder(node.right);
    }
}

二叉搜索树的第k个节点

public class Solution {
    int count = 0;
    TreeNode node = null;
    public int KthNode (TreeNode proot, int k) {
        // write code here
        dfs(proot, k);
        return node != null ? node.val : -1;
    }
    private void dfs(TreeNode root, int k){
        if(root == null || count > k) return;
        dfs(root.left, k);
        count++;
        if(count == k){
            node = root;
            return;
        }
        dfs(root.right, k);
    }
}

7 用两个栈实现队列

用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

解析

push栈和pop栈来完成

代码

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
        pushTopop();
    }
    
    public int pop() {
        pushTopop();
        return stack2.pop();
    }

    private void pushTopop(){
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
    }
}

8 斐波那契数列

解析

动态规划

代码

public class Solution {
    public int Fibonacci(int n) {
        int[] dp = new int[n + 1];
        if(n <= 2) return 1;
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i < n + 1; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

9 旋转数组的最小数字

有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

解析

具体做法:

  • step 1:双指针指向旋转后数组的首尾,作为区间端点。
  • step 2:若是区间中点值大于区间右界值,则最小的数字一定在中点右边。
  • step 3:若是区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。
  • step 4:若是区间中点值小于区间右界值,则最小的数字一定在中点左边。
  • step 5:通过调整区间最后即可锁定最小值所在。

代码

易错

缩小右界 只能与右边界进行比较。

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int l = 0, r = array.length - 1;
        while(l < r){
            int mid = l + (r - l) / 2;
            if(array[mid] > array[r]){
                l = mid + 1;
            }else if(array[mid] < array[r]){
                r = mid;
            }else{
                r--;
            }
        }
        return array[l];
    }
}

寻找峰值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3KyrWWK-1679312763455)(${img}/image-20230318215851854.png)]

public class Solution {
    public int findPeakElement (int[] nums) {
        // write code here
        int left = 0, right = nums.length - 1;
        while(left < right){
            int mid = (left + right) >> 1;
            if(nums[mid] > nums[mid + 1]){
                right = mid;
            }
            else{
                left = mid + 1;
            }
        }
        return left;
    }
}

10 矩阵中的路径

请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
输入:
[[a,b,c,e],[s,f,c,s],[a,d,e,e]],“abcced”
返回值:
true

解析

枚举矩阵的每个位置作为路径的起点,只要能找到对应字符串就直接返回 true。

具体在每次搜索中,可以依次尝试相邻未访问格子的字母,只要能和单词对应位置匹配,就继续向下搜索。

代码

public class Solution {
    int r = 0;
    int c = 0;
    public boolean hasPath(char[][] matrix, String word) {
        // write code here
        r = matrix.length;
        c = matrix[0].length;
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (dfs(matrix, i, j, word, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(char[][] matrix, int i, int j, String word, int index) {
        if (index == word.length()) return true;
        if (i < 0 || i >= r || j < 0 || j >= c || matrix[i][j] != word.charAt(index)) return false;
        char temp = matrix[i][j];
        matrix[i][j] = '.';
        return dfs(matrix, i + 1, j, word, index + 1) || dfs(matrix, i - 1, j, word, index + 1)
                || dfs(matrix, i, j + 1, word, index + 1)|| dfs(matrix, i, j - 1, word, index + 1) || (matrix[i][j] = temp) =='.';
    }
}   

11 机器人的运动范围

解析

地上有一个 rows 行和 cols 列的方格。坐标从 [0,0] 到 [rows-1,cols-1] 。一个机器人从坐标 [0,0] 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 threshold 的格子。 例如,当 threshold 为 18 时,机器人能够进入方格 [35,37] ,因为 3+5+3+7 = 18。但是,它不能进入方格 [35,38] ,因为 3+5+3+8 = 19 。请问该机器人能够达到多少个格子?

解析

和上题类似

代码

易错:添加isVisited

public class Solution {
    boolean[][] isVisited;
    int r;
    int c;
    int t;
    public int movingCount(int threshold, int rows, int cols) {
        isVisited = new boolean[rows][cols];
        r = rows;
        c = cols;
        t = threshold;
        return dfs(0, 0);
    }

    private int dfs(int i, int j){
        if(i >= r || i < 0 || j >= c || j < 0 || isVisited[i][j] || cal(i) + cal(j) > t) return 0;
        isVisited[i][j] = true;
        return 1 + dfs(i + 1, j) + dfs(i - 1, j) + dfs(i, j + 1) + dfs(i, j - 1);
    }

    private int cal(int num){
        int sum = 0;
        while(num > 0){
            sum += num % 10;
            num /= 10;
        }
        return sum;
    }
}

12 剪绳子

定义一个数组dp,其中dp[i]表示的是长度为i的绳子能得到的最大乘积。我们先把长度为i的绳子拆成两部分,一部分是j,另一部分是i-j,那么会有下面4种情况

1,j和i-j都不能再拆了

  • dp[i]=j*(i-j);

2,j能拆,i-j不能拆

  • dp[i]=dp[j]*(i-j);

3,j不能拆,i-j能拆

  • dp[i]=j*dp[i-j];

4,j和i-j都能拆

  • dp[i]=dp[j]*dp[i-j];

我们取上面4种情况的最大值即可。我们把它整理一下,得到递推公式如下

dp[i] = max(dp[i], (max(j, dp[j])) * (max(i - j, dp[i - j])));

public class Solution {
    public int cutRope(int target) {
        int[] dp = new int[target + 1];//dp[i]表示的是长度为i的绳子能得到的最大乘积
        dp[1] = 1;
        for (int i = 2; i <= target; i++) {
            for (int j = 1; j < i; j++) {
                dp[i] = Math.max(dp[i], (Math.max(j, dp[j])) * (Math.max(i - j, dp[i - j])));
            }
        }
        return dp[target];
    }
}

跳台阶扩展问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。

public class Solution {
    public int jumpFloorII(int target) {
        int[] dp = new int[target + 1];
        //初始化前面两个
        dp[0] = 1; 
        dp[1] = 1;
        //依次乘2
        for(int i = 2; i <= target; i++) 
            dp[i] = 2 * dp[i - 1];
        return dp[target];
    }
}

public class Solution {
    public int jumpFloorII(int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i < target + 1; i++) {
            for (int j = 0; j < i; j++) {
                dp[i] += dp[j]; //dp[i] = dp[0] + dp[1] + .. +dp[i - 1]
            }
        }
        return dp[target];
    }
}

13 二进制中1的个数

public class Solution {
    public int NumberOf1(int n) {
        int res = 0;
        while(n != 0){
            n = n & (n - 1);//将最后一位从0变成1
            res++;
        }
        return res;
    }
}

求1+2+3+…+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

public class Solution {
    public int Sum_Solution(int n) {
        boolean flag = (n > 0) && (n += Sum_Solution(n - 1)) > 0;
        return n;
    }
}

14 数值的整数次方

public class Solution {
    public double Power(double base, int exponent) {
        if (exponent < 0) {
            base = 1 / base;
            exponent = -exponent;
        }
        return pow(base, exponent);
    }
	//快速幂
    private double pow(double base, int exponent) {
        double res = 1;
        while (exponent != 0) {
            //可以再往上乘一个
            if ((exponent & 1) != 0) {
                res *= base;
            }
            //减少乘次数
            exponent = exponent >> 1;
            //叠加
            base *= base;
        }
        return res;
    }
}

15 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

import java.util.*;
public class Solution {
    public int[] printNumbers (int n) {
        //找到该n+1位数的最小数字
        int end = 1;
        for(int i = 1; i <= n; i++)
            end *= 10;
        //从1遍历到n+1位数的最小数字输出
        int[] res = new int[end - 1];
        for(int i = 1; i < end; i++)
            res[i - 1] = i;
        return res;
    }
}

16 删除链表的节点

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

import java.util.*;
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;
    }
}

删除链表中重复的结点

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        ListNode dummy = new ListNode(-1);
        ListNode help = dummy;
        while (pHead != null) {
            // 进入循环时,确保了 pHead 不会与上一节点相同
            if (pHead.next == null || pHead.next.val != pHead.val) {
                help.next = pHead;
                help = help.next;
            }else{
                // 如果 pHead 与下一节点相同,跳过相同节点(到达「连续相同一段」的最后一位)
                while (pHead.next != null && pHead.val == pHead.next.val) pHead = pHead.next;
            }
            pHead = pHead.next;
        }
        help.next = null;
        return dummy.next;
    }
}

17 正则表达式匹配

易错

i从0开始,因为有a*这种存在

public class Solution {
    public boolean match(String s, String p) {
        //dp[i][j] 表示 s的前i个字符与 p中的前j个字符是否能够匹配
        int n = s.length();
        int m = p.length();
        boolean[][] dp = new boolean[n + 1][m + 1];
        dp[0][0] = true;
        for(int i = 0; i < n + 1; i++){
            for(int j = 1; j < m + 1; j++){
                if(p.charAt(j - 1) != '*'){
                    dp[i][j] = i >= 1 && j >= 1 && dp[i - 1][j - 1] && isMath(s, p, i, j) ;
                }else{
                    //* 匹配零个
                    boolean mathZero = j >= 2 && dp[i][j - 2];
                    //* 匹配多个
                    boolean matchMany = i >= 1 && j >= 1 && dp[i - 1][j] && isMath(s, p, i, j - 1);
                    dp[i][j] = mathZero || matchMany;
                }
            }
        }
        return dp[n][m];
    }

    private boolean isMath(String s, String p, int i, int j){
        return s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.';
    }
}

18 表示数值的字符串

public boolean isNumeric(String str) {
    int n = str.length();
    int index = 0;
    boolean hasNum = false, hasE = false;
    boolean hasSign = false, hasDot = false;
    while (index < n && str.charAt(index) == ' ') index++;
    while (index < n) {
        while (index < n && str.charAt(index) >= '0' && str.charAt(index) <= '9') {
            index++;
            hasNum = true;
        }
        if (index == n) {
            break;
        }
        char c = str.charAt(index);
        if (c == 'e' || c == 'E') {
            if (hasE || !hasNum) {
                return false;
            }
            hasE = true;
            hasNum = false;
            hasSign = false;
            hasDot = false;
        } else if (c == '+' || c == '-') {
            if (hasSign || hasNum || hasDot) {
                return false;
            }
            hasSign = true;
        } else if (c == '.') {
            if (hasDot || hasE) {
                return false;
            }
            hasDot = true;
        } else if (c == ' ') {
            break;
        } else {
            return false;
        }
        index++;
    }
    while (index < n && str.charAt(index) == ' ') index++;
    return hasNum && index == n;
}

19 调整数组顺序使奇数位于偶数前面(一)

输入一个长度为 n 整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前面部分,所有的偶数位于数组的后面部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

import java.util.*;
public class Solution {
    public int[] reOrderArray (int[] array) {
        int n = array.length;
        int[] res = new int[n];
        //统计奇数个数
        int odd = 0; 
        //遍历统计
        for(int i = 0; i < n; i++){ 
            if(array[i] % 2 == 1)
                odd++;
        }
        //x与y分别表示答案中奇偶数的坐标
        int x = 0, y = odd; 
        for(int i = 0; i < n; i++){
            //奇数在前
            if(array[i] % 2 == 1){ 
                res[x] = array[i];
                x++;
            //偶数在后
            }else{ 
                res[y] = array[i];
                y++;
            }
        }
        return res;
    }
}

调整数组顺序使奇数位于偶数前面(二)

//除了上面的方法外还有
public int[] reOrderArrayTwo(int[] array) {
    int l = 0, r = array.length - 1, tmp = 0;
    // 终止条件:左右指针相遇
    while (l < r) {
        if ((array[l] & 1) == 1) {
            // 奇数:左指针往右挪
            l++;
        } else {
            // 偶数:交换后,右指针往左挪
            tmp = array[l];
            array[l] = array[r];
            array[r] = tmp;
            r--;
        }
    }
    return array;
}    

20 链表中倒数最后k个结点

public ListNode FindKthToTail (ListNode pHead, int k) {
    // write code here
    ListNode fast = pHead;
    ListNode slow = pHead;
    for (int i = 0; i < k; i++) {
        if(fast == null) return null;
        fast = fast.next;
    }
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

21 链表中环的入口结点

public ListNode EntryNodeOfLoop(ListNode pHead) {
    if(pHead == null) return null;
    ListNode fast = pHead;
    ListNode slow = pHead;
    while(fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        if(slow == fast){
            fast = pHead;
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return fast;
        }
    }
    return null;
}

22 反转链表

public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode next = null;
        ListNode last = null;
        while (head != null) {
            next = head.next;
            head.next = last;
            last = head;
            head = next;
        }
        return last;
    }
}

23 合并两个排序的链表

public class Solution {
    public ListNode Merge(ListNode list1, ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
        ListNode help = new ListNode(Integer.MIN_VALUE);
        ListNode cur = help;
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                cur.next = list1;
                list1 =  list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        cur.next = list1 == null ? list2 : list1;
        return help.next;
    }
}

24 树的子结构

public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root1 == null || root2 == null) return false;
        if(judge(root1, root2)) return true; //当前节点是子树
        //先序遍历
        return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }

    private boolean judge(TreeNode root1, TreeNode root2) {
        if(root2 == null) return true;//子树到头了
        if(root1 == null) return false;//没遍历完子树
        if(root1.val != root2.val) return false;
        return judge(root1.left, root2.left) && judge(root1.right, root2.right);
    }
}

25 二叉树的镜像

public TreeNode Mirror (TreeNode pRoot) {
    // 先序遍历,从顶向下交换
    if(pRoot == null) return null;
    TreeNode temp = pRoot.left;
    pRoot.left = pRoot.right;
    pRoot.right = temp;
    Mirror(pRoot.left);//左子树交换
    Mirror(pRoot.right);//右子树交换
    return pRoot;
}

26 对称的二叉树

public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        if(pRoot == null) return true;
        return dfs(pRoot.left, pRoot.right);
    }

    private boolean dfs(TreeNode left, TreeNode right) {
        if(left == null && right == null) return true;
        if(left == null || right == null || left.val != right.val) return false;
        return dfs(left.left, right.right) && dfs(left.right, right.left);
    }
}

27 顺时针打印矩阵

public ArrayList<Integer> printMatrix(int[][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
        int left = 0, right = matrix[0].length - 1, top = 0, down = matrix.length - 1;
        while (left <= right && top <= down) {
            for (int i = left; i <= right; i++) {
                res.add(matrix[top][i]);
            }
            top++;
            if (top > down) break;
            for (int i = top; 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 (top > down) break;
            for (int i = down; i >= top; i--) {
                res.add(matrix[i][left]);
            }
            left++;
            if (right < left) break;
        }
        return res;
    }

28 包含min函数的栈

public class Solution {

    Stack<Integer> stack1 = new Stack<>(); //用于栈的push 与 pop
    Stack<Integer> stack2 = new Stack<>(); //min栈   
    public void push(int node) {
        stack1.push(node);
        if(stack2.isEmpty()||stack2.peek()>node){
            stack2.push(node);
        }else{
            stack2.push(stack2.peek());//保持两个栈长度一样,方便pop
        }

    }
    
    public void pop() {
        stack1.pop();
        stack2.pop();
    }
    
    public int top() {
        return stack1.peek();
    }
    
    public int min() {
        return stack2.peek();
    }
}

29 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> stack = new Stack<>();//辅助栈

        for (int i = 0, j = 0; i < pushA.length; i++) {
            stack.push(pushA[i]);//重新压栈
            while (!stack.isEmpty() && stack.peek() == popA[j]) {//碰到自己就弹出,交给下面的
                stack.pop();
                j++;
            }
        }
        return stack.isEmpty();
    }
}

30 从上往下打印二叉树

public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> res = new ArrayList<>();
        if (root == null) return res;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int n = queue.size();
            while (n-- > 0) {
                TreeNode node = queue.poll();
                res.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
        }
        return res;
    }
}

31 二叉搜索树的后序遍历序列

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder, 0, postorder.length - 1);
    }
    boolean recur(int[] postorder, int i, int j) {
        if(i >= j) return true;
        int p = i;
        while(postorder[p] < postorder[j]) p++;
        int m = p;
        while(postorder[p] > postorder[j]) p++;
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
    }
}

32 二叉树中和为某一值的路径(二)

题目中是给的到叶子结点

public class Solution {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    ArrayList<Integer> temp = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int expectNumber) {
        if(root == null) return res;
        dfs(root, expectNumber);
        return res;
    }

    private void dfs(TreeNode root, int expectNumber) {
        if(root == null) return;
        //先序遍历
        expectNumber = expectNumber - root.val;
        temp.add(root.val);
        if(expectNumber == 0 && root.left == null && root.right == null){
            res.add(new ArrayList<>(temp));
        }
        dfs(root.left, expectNumber);
        dfs(root.right, expectNumber);
        temp.remove(temp.size() - 1);
    }
}

33 复杂链表的复制

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null) return null;
        Node cur = head;
        Map<Node, Node> map = new HashMap<>();
        // 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while(cur != null) {
            map.put(cur, new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        // 4. 构建新链表的 next 和 random 指向
        while(cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        // 5. 返回新链表的头节点
        return map.get(head);
    }
}

34 二叉搜索树与双向链表

public class Solution {
    private TreeNode head = null;//起始位置
    private TreeNode pre  = null;//不断更新的位置
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree == null) return null;
        //中序遍历即为有序
        Convert(pRootOfTree.left);
        if (head == null) {
            head = pRootOfTree;//最左端
            pre = head;
        } else {
            pre.right = pRootOfTree;//右指针指向后继
            pRootOfTree.left = pre;//左指针指向前继
            pre = pRootOfTree; //更新位置
        }
        Convert(pRootOfTree.right);
        return head;
    }
}

35 序列化二叉树

import java.util.*;
public class Solution {
    StringBuilder sb = new StringBuilder();

    String Serialize(TreeNode root) {
        SerializeHelp(root, sb);
        return sb.toString();
    }

    TreeNode Deserialize(String str) {
        String[] values = str.split("_");
        //队列中存放分隔后的数据,value和#
        Queue<String> queue = new LinkedList<>();
        for (int i = 0; i < values.length; i++) {
            queue.offer(values[i]);
        }
        return DeserializeHelp(queue);
    }

    void SerializeHelp(TreeNode node, StringBuilder sb) {
        //先序遍历 null 为#_ ,其他分隔符为_
        if (node == null) {
            sb.append("#_");
            return; //结束
        }
        sb.append(node.val + "_");
        SerializeHelp(node.left, sb);
        SerializeHelp(node.right, sb);
    }

    TreeNode DeserializeHelp(Queue<String> queue) {
        String value = queue.poll();
        if (value.equals("#")) return null; //遇到# 表示空节点
        TreeNode node = new TreeNode(Integer.valueOf(value));
        node.left = DeserializeHelp(queue);
        node.right = DeserializeHelp(queue);
        return node;
    }
}

36 字符串的排列

import java.util.*;
public class Solution {
    ArrayList<String> res = new ArrayList<String>();
    StringBuilder sb = new StringBuilder();
    boolean[] used;

    public ArrayList<String> Permutation(String str) {
        if (str == null || str.length() == 0) return res;
        char[] charStr = str.toCharArray();
        //字典序
        Arrays.sort(charStr);
        //标记每个位置的字符是否被使用过
        used = new boolean[str.length()];
        recursion(charStr); //递归获取
        return res;
    }
    public void recursion(char[] charStr) {
        if (sb.length() == charStr.length) {
            res.add(new String(sb));//临时字符串满了加入输出
            return;
        }
        for (int i = 0; i < charStr.length; i++) {
            if (used[i]) continue; //如果该元素已经被加入了则不需要再加入了
            //当前的元素charStr[i]与同一层的前一个元素charStr[i-1]相同且charStr[i-1]没有用过(实际上已经用过了)
            if (i > 0 && charStr[i - 1] == charStr[i] && !used[i - 1]) continue;
            used[i] = true;
            sb.append(charStr[i]);
            recursion(charStr);
            //回溯
            used[i] = false;
            sb.deleteCharAt(sb.length() - 1);
        }
    }
}

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

public int MoreThanHalfNum_Solution(int[] array) {
    int count = 0, res = 0;
    for (int num : array) {
        if (count == 0) res = num;
        if (num != res) count--;
        else count++;
    }
    return res;
}

38 最小的K个数

给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。

import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        quickSort(input, 0, input.length - 1, k); //最小的k个数字
        ArrayList<Integer> res = new ArrayList<Integer>();
        for(int i = 0; i < k; i++){
            res.add(input[i]);
        }
        return res;
    }

    private void quickSort(int[] input, int l, int r, int k){
        if(l >= r) return;
        int random = l + (int)(Math.random() * (r - l + 1));
        swap(input, random, r);
        int p = partion(input, l, r);
        if(p < k) quickSort(input, p + 1, r, k);//说明不够
        else if(p > k) quickSort(input, l, p - 1, k);//超了
        else return;
    }

    private int partion(int[] input, int l, int r){
        int left = l - 1;
        int cmp = input[r];
        for(int i = l; i < r; i++){
            if(input[i] <= cmp){
                swap(input, ++left, i);
            }
        }
        swap(input, left + 1, r);
        return left + 1;
    }

    private void swap(int[] input, int a, int b){
        int temp = input[a];
        input[a] = input[b];
        input[b] = temp;
    }
}

39 数据流中的中位数

import java.util.*;

public class Solution {

    PriorityQueue<Integer> min = new PriorityQueue<>();
    PriorityQueue<Integer> max = new PriorityQueue<>((x, y) -> y - x);

    public void Insert(Integer num) {
        max.offer(num);
        min.offer(max.poll());
        if(max.size() < min.size()){
            max.offer(min.poll());
        }
    }

    public Double GetMedian() {
        if(min.size() == max.size()){
            return (min.peek() + max.peek()) / 2.0;
        }else {
            return max.peek() / 1.0;
        }
    }


}	

40 连续子数组的最大和

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;
}

41 整数中1出现的次数(从1到n整数中1出现的次数)

输入一个整数 n ,求 1~n 这 n 个整数的十进制表示中 1 出现的次数
例如, 1~13 中包含 1 的数字有 1 、 10 、 11 、 12 、 13 因此共出现 6 次

注意:11 这种情况算两次

解析

对于整数n,将这个整数分为三部分:当前位数字cur,更高位数字high,更低位数字low,如:对于n=21034,当位数是十位时,cur=3,high=210,low=4。
我们从个位到最高位 依次计算每个位置出现1的次数:
在计算时,会出现三种情况
1)当前位的数字等于0时,例如n=21034,在百位上的数字cur=0,百位上是1的情况有:00100-00199,01100-01199,……,20100-20199。一共有21100种情况,即high*100;
2)当前位的数字等于1时,例如n=21034,在千位上的数字cur=1,千位上是1的情况有:01000-01999,11000-11999,21000-21034。一共有2
1000+(34+1)种情况,即high*1000+(low+1)
3)当前位的数字大于1时,例如n=21034,在十位上的数字cur=3,十位上是1的情况有:00010-00019,……,21010-21019。一共有(210+1)*10种情况,即(high+1)*10

public int NumberOf1Between1AndN_Solution(int n) {
    int count = 0;
    for (int i = 1; i <= n; i *= 10) {  //i代表位数
        int high = n / (i * 10); //更高位数字
        int low = (n % i);  //更低位数字
        int cur = (n / i) % 10;  //当前位数字

        if (cur == 0) {
            count += high * i;
        } else if (cur == 1) {
            count += high * i + (low + 1);
        } else {
            count += (high + 1) * i;
        }
    }
    return count;
}

42 数字序列中某一位的数字

数字以 0123456789101112131415… 的格式作为一个字符序列,在这个序列中第 2 位(从下标 0 开始计算)是 2 ,第 10 位是 1 ,第 13 位是 1 ,以此类题,请你输出第 n 位对应的数字。

public int findNthDigit(int n) {
    int dig = 1;//表示当前位数
    long start = 1;//表示起始数字
    long count = 9;//最大数量,结束条件
    while (n > count) {
        n -= count;//表示进入下一组
        dig++;
        start = start * 10;
        count = 9 * dig * start;
    }
    //计算得到当前数字
    String s = "" + (start + (n - 1) / dig);
    //选择的下标
    int idx = (n - 1) % dig;
    return s.charAt(idx) - '0';
}

43 把数组排成最小的数

输入一个非负整数数组numbers,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

例如输入数组[3,32,321],则打印出这三个数字能排成的最小数字为321323。

1.输出结果可能非常大,所以你需要返回一个字符串而不是整数
2.拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0

import java.util.*;

public class Solution {
    public String PrintMinNumber(int[] nums) {
        if (nums == null || nums.length == 0) return "";
        String[] ss =  new String[nums.length];
        for (int i = 0; i < nums.length; i++) {
            ss[i] = String.valueOf(nums[i]);
        }
        Arrays.sort(ss, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));
        StringBuilder sb = new StringBuilder();
        for (String s : ss) {
            sb.append(s);
        }
        return sb.toString();
    }
}

44 把数字翻译成字符串

public class Solution {
    public int solve (String nums) {
        // write code here
        int n = nums.length();
        String s = " " + nums;
        char[] cs = s.toCharArray();
        //dp[i] 第i个位置有多少种译码方式
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int i = 1; i < n + 1; i++) { 
            // a : 代表「当前位置」单独形成 item
            // b : 代表「当前位置」与「前一位置」共同形成 item
            int a = cs[i] - '0', b = (cs[i - 1] - '0') * 10 + (cs[i] - '0');
            // 如果 a 属于有效值,那么 dp[i] 可以由 dp[i - 1] 转移过来(一种结果),0没有映射
            if (1 <= a && a <= 9) dp[i] = dp[i - 1];
            // 如果 b 属于有效值,那么 dp[i] 可以由 dp[i - 2] 或者 dp[i - 1] & dp[i - 2] 转移过来(另一个结果)
            if (10 <= b && b <= 26) dp[i] += dp[i - 2];
        }
        return dp[n];
    }
}

45 礼物的最大价值

public int maxValue (int[][] grid) {
    // write code here
    int r = grid.length;
    int c = grid[0].length;
    int[][] dp = new int[r][c];
    dp[0][0] = grid[0][0];
    for (int i = 1; i < r; i++) {
        dp[i][0] = grid[i][0] + dp[i - 1][0];
    }
    for (int i = 1; i < c; i++) {
        dp[0][i] = grid[0][i] + dp[0][i - 1];
    }
    for (int i = 1; i < r; i++) {
        for (int j = 1; j < c; j++) {
            dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
        }
    }
    return dp[r - 1][c - 1];
}

46 最长不含重复字符的子字符串

public int lengthOfLongestSubstring (String s) {
    // write code here
    if(s == null || s.length() == 0) return 1;
    HashMap<Character, Integer> map = new HashMap<>();
    char[] cs = s.toCharArray();
    int res = 0;
    for (int i = 0, j = 0; i < cs.length; i++) {
        map.put(cs[i], map.getOrDefault(cs[i], 0) + 1);
        while (map.get(cs[i]) > 1) {
            map.put(cs[j], map.get(cs[j]) - 1);
            j++;
        }
        if (i - j + 1 > res) {
            res = i - j + 1;
        }
    }
    return res;
}

47 丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第 n个丑数。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index == 0) return 0;
        int a = 0, b = 0, c = 0;//下标号
        int[] dp = new int[index];
        dp[0] = 1;
        for (int i = 1; i < index; i++) {
            //历史的丑数
            // 第a丑数个数需要通过乘2来得到下个丑数,第b丑数个数需要通过乘2来得到下个丑数,同理第c个数
            int na = dp[a] * 2, nb = dp[b] * 3, nc = dp[c] * 5;
            dp[i] = Math.min(Math.min(na, nb), nc);
            if(dp[i] == na) a++;
            if(dp[i] == nb) b++;
            if(dp[i] == nc) c++;
        }
        return dp[index - 1];
    }
}

48 第一个只出现一次的字符

在一个长为字符串中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)

import java.util.*;

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        char[] chars = str.toCharArray();
        HashMap<Character, Integer> map = new HashMap<>();
        for (char c : chars) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (int i = 0; i < str.length(); i++) {
            if(map.get(str.charAt(i)) == 1){
                return i;
            }
        }
        return -1;
    }
}

49 数组中的逆序对

public class Solution {
    public int InversePairs(int [] array) {
        //归并算法
        //计数条件是左边大于右边
        if (array == null || array.length < 2) return 0;
        return (int)process(array, 0, array.length - 1) % 1000000007;
    }
    public long process(int [] array, int left, int right) {
        if (left >= right) return 0;
        int mid = (left + right) >> 1;
        long leftnum = process(array, left, mid);//左边排好序
        long rightnum = process(array, mid + 1, right);//右边排好序
        long mergernum = merge(array, left, mid, right); //利用外排序将左右两边排好序
        return leftnum + rightnum + mergernum;
    }

    public long merge(int [] array, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int helpIndex = 0;//外排索引
        int count  = 0;//逆序对数量
        int leftHead = left;//左边头索引
        int rightHead = mid + 1;//右边头索引
        while (leftHead <= mid && rightHead <= right) {
            if (array[leftHead] <= array[rightHead]) {
                help[helpIndex++] = array[leftHead++];
            } else {
                help[helpIndex++] = array[rightHead++];
                count = count + mid - leftHead + 1;
            }
        }
        while (leftHead <= mid) {
            help[helpIndex++] = array[leftHead++];
        }
        while (rightHead <= right) {
            help[helpIndex++] = array[rightHead++];
        }
        //覆盖回去
        for (int i = 0; i < help.length; i++) {
            array[left + i] = help[i];
        }
        return count % 1000000007;
    }
}

50 两个链表的第一个公共结点

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while (p1 != p2) {
            p1 = p1 != null ? p1.next : pHead2;
            p2 = p2 != null ? p2.next : pHead1;
        }
        return p1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值