剑指offer解题笔记

求职在即,《剑指offer》作为大家都推荐的一本应试宝典算法相关资料,确实也有刷一刷的必要。很多题目都比较经典,也涵盖了大多数的算法和数据结构。把自己刷题的过程做一个总结,权当是一个笔记。

下面为本人在LeetCode上面刷的题目笔记,给出所有代码为本人提交Accepted后记录。

数组中重复的数字

题目描述

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

解题思路

运用HashMap,将数组中的数一一存入,同时比较是否map中存在,已存在的话就输出,不存在则存入map中并赋给初始值1

class Solution {
    public int findRepeatNumber(int[] nums) {
              int n=nums.length;
              int result=0;
		        Map<Integer, Integer> map = new HashMap<>();
		      for (int i = 0; i < n; i++) {
			  if (map.containsKey(nums[i])) {
				result=nums[i];
                break;
			   } else {
				map.put(nums[i], 1);
			}
		}
        return result;
    }
   
}

其他解法:HashSet

第一个只出现一次的字符

题目描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)。

解题思路

  • 要求只出现一次的第一个字符,就需要去计数每一个字符出现了几次。一般都是用哈希表,但是字符往多了数按照扩展的ASCII码也就256个,就可以使用int[] count来代替哈希表:键就是字符对应的数,值就是该字符出现的次数。
  • 第一次遍历计数每个字符出现的次数for(char c : chars) count[c]++;。第二次遍历按顺序去查看该字符出现了几次,如果该字符出现了1次,它就是第一个仅出现一次的字符,直接返回。
  • 如果第二次遍历没有返回,就说明没有仅出现一次的字符,返回’ '。
    时间复杂度为O(n),空间复杂度为O(n)(因为使用了辅助的字符数组).
class Solution {
    public char firstUniqChar(String s) {
        int[] count=new int[256];
        char[] chars=s.toCharArray();
        for(char c:chars){
            count[c]++;
        }
        for(char c:chars){
            if(count[c]==1)
            return c;
        }
        return ' ';
    }
}

其他解法:HashMap

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

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解题思路

排序 或者不断记录出现次数

public class Solution {
    public int majorityElement(int[] nums) {
        //记录每个数字的出现次数
        int[] count = new int[10];
        for(int i = 0; i < nums.length; i++){
            count[nums[i]]++;
            if(count[nums[i]] > nums.length/2){
                return nums[i];
            }
        }
        return 0;
    }
}
不能有负数,不能有大数,否则数组越界,简单方法就排序
class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length/2];
    }
}

其他方法:比排序高效的算法

最小的k个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

解题思路

简单题,主要复习了java数组拷贝知识和排序算法。

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        Arrays.sort(arr);
        int[] result=new int[k];
        result=Arrays.copyOf(arr,k);
        return(result);
    }
}
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
    ArrayList<Integer> result = new ArrayList<Integer>();
    if(k<= 0 || k > input.length)
        return result;
    //初次排序,完成k个元素的排序
    for(int i = 1; i< k; i++){
        int j = i-1;
        int unFindElement = input[i];
        while(j >= 0 && input[j] > unFindElement){
            input[j+1] = input[j];
            j--;
        }
        input[j+1] = unFindElement;
    }
    //遍历后面的元素 进行k个元素的更新和替换
    for(int i = k; i < input.length; i++){
        if(input[i] < input[k-1]){
            int newK = input[i];
            int j = k-1;
            while(j >= 0 && input[j] > newK){
                input[j+1] = input[j];
                j--;
            }
            input[j+1] = newK;
        }
    }
    //把前k个元素返回
    for(int i=0; i < k; i++)
        result.add(input[i]);
    return result;
}

其他算法:TOPK问题解法

笔记记录:java数组复制

斐波那契数列

题目描述

求斐波那契数列,斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

解题思路

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

递归算法
public class Solution {
    public int Fibonacci(int n) {
        if(n<=1){
            return n;
        }
        return Fibonacci(n-1) + Fibonacci(n-2);
    }
}

字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

解题思路

class Solution {
    public String[] permutation(String s) {
        char[] arr = s.toCharArray();
        Set<String> ans = new HashSet<>();
        f(ans, 0, arr);
        return ans.toArray(new String[ans.size()]);

    }

    //与其说是递归,不如说是树形遍历
    void f(Set ans, int position, char[] arr){
        if(position == arr.length)
            ans.add(String.valueOf(arr));
        for(int i = position; i<arr.length; i++){
            // 对数组swap的过程就是排列的过程,
            // 在for循环中swap,每次swap后,就会有新的元素排在第一位
            swap(arr, position, i);
            f(ans, position+1, arr);
            swap(arr, position, i);
        }
    }

    void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

其他算法:回溯法

字典序相关:我的博客记录

求幂
(int)Math.pow(10,n);

数值的整数次方

题目描述

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

解题思路

递归调用(分治思想),要注意的一点是,虽然题目中告诉我们不需要考虑大数问题,但是给出的 n 可以取到 -2147483648−2147483648(整型负数的最小值),因此,在编码的时候,需要将 n 转换成 long 类型。

class Solution {
    public double myPow(double x, int n) {
        long N=n;
        if(n>=0){
            return myPow(x,N);
        }else{
            return 1/myPow(x,-N);
        }
    }
    private double myPow(double x,long n){
        if(n==0){
            return 1;
        }
        if(n==1){
            return x;
        }
        if(n%2==0){
            double square=myPow(x,n/2);
            return square*square;
        }else{
             double square=myPow(x,(n-1)/2);
             return square*square*x;
        }
    }
}

青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

解题思路

此类求 多少种可能性 的题目一般都有 递推性质 ,即f(n) 和 f(n-1)…f(1) 之间是有联系的。

设跳上 n 级台阶有 f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。
当为 1 级台阶: 剩 n-1 个台阶,此情况共有 f(n−1) 种跳法;
当为 2 级台阶: 剩 n−2 个台阶,此情况共有 f(n-2) 种跳法。
f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2)。

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

注意直接存储会时间超限,所以使用数组记录计算出来的值

二进制中1的个数

题目描述

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。

解题思路

把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

也可以使用java自带的Bitcount直接得到结果: return Integer.bitCount(n);

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count=0;
        while(n!=0){
            count++;
            n=n&(n-1);
        }
        return count;
    }
}

删除链表的节点

题目描述

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

解题思路

简单实现链表的删除操作,即改变指针指向。
注意不能使用java不能使用p->next,只能p.next

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        
        if(head == null) return null;
        if(head.val == val) return head.next;

        ListNode p = head;
        while(p.next.val != val) p = p.next;

        p.next = p.next.next;
        return head;

    }
}

从尾到头打印链表

题目描述

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

解题思路

链表特点: 只能从前至后访问每个节点。

题目要求: 倒序输出节点值。

这种 先入后出 的需求可以借助 栈 来实现。

算法流程:

入栈: 遍历链表,将各节点值 push 入栈。(​Java​借助 LinkedList 的addLast()方法)。

出栈: 将各节点值 pop 出栈,存储于数组并返回。(Java ​新建一个数组,通过 popLast() 方法将各元素存入数组,实现倒序输出)。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        LinkedList<Integer> stack=new LinkedList<Integer>();

        while(head!=null){
            stack.addLast(head.val);
            head=head.next;
        }

        int[] res=new int[stack.size()];
        for(int i=0;i<res.length;i++){
            res[i]=stack.removeLast();
        }
        return res;
    }
}

从尾到头打印链表

题目描述

解题思路

输入:

[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
这一行表示输入的操作

[ [],[3],[],[] ]
这个表示每一行代码操作后对应的结果

举例:
CQueue 表示新建一个CQueue对象,对应的结果为[]。
appendTail 表示执行一个appendTail()操作,对应要被操作的元素为3,,对应的结果为[3]
deleteHead 表示执行一个deleteHead操作,对应的结果为[]
deleteHead 表示执行一个deleteHead操作,对应的结果为[]

以上的输入其实是一个代码执行的步骤描述与其对应结果或操作。
并不是说,上面的“输入”表示的输入程序验证的数据.

使用java的要注意,如果使用Stack的方式来做这道题,会造成速度较慢; 原因的话是Stack继承了Vector接口,而Vector底层是一个Object[]数组,那么就要考虑空间扩容和移位的问题了。 可以使用LinkedList来做Stack的容器,因为LinkedList实现了Deque接口,所以Stack能做的事LinkedList都能做,其本身结构是个双向链表,扩容消耗少。

两个栈,叫他兄弟栈好了,每次入队都是哥哥出,但要出队的时候,哥哥找弟弟帮忙把栈底的元素(也就是队头)出队。

class CQueue {

    LinkedList<Integer> stack1;
    LinkedList<Integer> stack2;

    public CQueue() {
        stack1=new LinkedList<Integer>();
        stack2=new LinkedList<Integer>();

    }
    
    public void appendTail(int value) {
        stack1.add(value);  
    }
    
    public int deleteHead() {
        if(stack2.isEmpty()){
            if(stack1.isEmpty()) return -1;
            while(!stack1.isEmpty()){
                stack2.add(stack1.pop());
            }
            return stack2.pop();
        }
        else return stack2.pop();

    }
}

解法说明:官方解答


旋转数组的最小数字

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

解题思路

二分法

/*class Solution {
    public int minArray(int[] nums) {
        int min=nums[0];
        for(int i=1;i
class Solution {
    public int minArray(int[] numbers) {
        int i = 0, j = numbers.length - 1;
        while (i < j) {
            int m = (i + j) / 2;
            if (numbers[m] > numbers[j]) i = m + 1;
            else if (numbers[m] < numbers[j]) j = m;
            else j--;
        }
        return numbers[i];
    }
}

二叉搜索树的最近公共祖先

题目描述

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例见原题。

解题思路

对于二叉搜索树:

根据以上定义,若 root是 p,q 的 最近公共祖先 ,则只可能为以下情况之一:

  • p 和 q 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
  • p = root,且 q 在 root 的左或右子树中;
  • q = root,且 p 在 root 的左或右子树中;
  1. 若 root.val < p.val ,则 p 在 root 右子树 中;
  2. 若 root.val > p.val ,则 p 在 root 左子树 中;
  3. 若 root.val = p.val ,则 p 和 root 指向 同一节点 。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root!=null){
            if(root.val>p.val&&root.val>q.val){
            root=root.left;
        }else if(root.val<p.val&&root.val<q.val){
            root=root.right;
        }
        else break;
        }
         return root;
    }
}

两个链表的第一个公共节点

题目描述

输入两个链表,找出它们的第一个公共节点。示例:

  • 输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
  • 输出:Reference of the node with value = 2

解题思路

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode h1 = headA, h2 = headB;
        while (h1 != h2) {

            h1 = h1 == null ? headB : h1.next;
            h2 = h2 == null ? headA : h2.next;
        }

        return h1;  
    }
}

此题太浪漫了!两个结点去对方的轨迹中寻找对方的身影,只要二人有交集,就终会相遇❤

复杂链表的复制

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。

解题思路

使用HashMap存储子链表的值,然后根据存储的map get存储指针指向关系。只学了第一种解题方案,但效率挺高的。

/**
     * 哈希表
     * 空间复杂度 O(N)
     */
class Solution {
    public Node copyRandomList(Node head) {
         if (head == null) {
            return head;
        }

        HashMap<Node, Node> map = new HashMap<Node, Node>();
        Node cur = head;
        //map中存的是(原节点,拷贝节点)的一个映射
        while (cur != null) {
            map.put(cur, new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        //将拷贝的新的节点组织成一个链表
        //由原链表的关系,生成新链表的关系
        while (cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
        }
        return map.get(head);
    }
}

原地修改,空间复杂度为O(1)

/**
     * 空间复杂度O(1)
     * 1.将复制的节点放在原节点之后
     * 2.将复制节点的random节点放在原节点的random节点之后
     * 3.将二者分开
     */
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }
        //将拷贝节点放到原节点后面,例如1->2->3这样的链表就变成了这样1->1'->2'->3->3'
        for (Node node = head, copy = null; node != null; node = node.next.next) {
            copy = new Node(node.val);
            copy.next = node.next;
            node.next = copy;
        }
        //拷贝节点的random信息
        for (Node node = head; node != null; node = node.next.next) {
            if (node.random != null) {
                node.next.random = node.random.next;
        //node.next 是 node 复制出来的节点 ,node.random.next 是 node.random 复制出来的节点
            }
        }
        //分离拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表,后者就是答案
        Node newHead = head.next;
        for (Node node = head, temp = null; node != null && node.next != null;) {
            temp = node.next;
            node.next = temp.next;
            node = temp;
        }

        return newHead;
    }
}

试题讲解:up主讲解

翻转单词顺序

题目描述

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

解题思路

trim()函数删除首位空格;

split函数为java字符串分隔符:详细用法

如果字符串前面有空格 split() 会产生一个"",如果中间有连续的三个空格 会产生两个""。

使用equals而不用==,因为比较的是内存地址即是否为同一个对象,equals比较的是内存空间里的内容是否相同。所以String类型要用""无效 要使用equals() 方法判断

class Solution {
    public String reverseWords(String s) {
        String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
        StringBuilder res = new StringBuilder();
        for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
            if(strs[i].equals("")) continue; // 遇到空单词则跳过
            res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
        }
        return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
    }
}

题解:题目讲解

左旋转字符串

题目描述

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

解题思路

直接使用切片函数return s.subString(n,s.length())+s.subString(0,n);

使用StringBulider保存变化长度字符串,注意原string字符串s[i]不能使用要用s.charAt[i]。

class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder str=new StringBuilder();
        for(int i=n;i<s.length();i++){
            str.append(s.charAt(i));
        }
        for(int i=0;i<n;i++){
            str.append(s.charAt(i));
        }
        return str.toString();
    }
}

效率不高QAQ,就这样吧

把字符串转换成整数

题目描述

这题麻烦点就在于边界问题,最开始的空格要处理,然后负号要标记,遇到正负号index++后,还要判断int数组边界问题。int的范围[-2147483648,2147483647]。

解题思路

判断好边界情况

class Solution {
    public int strToInt(String str) {
        str=str.trim();
        if(str.length()==0) return 0;

        char c=str.charAt(0);
        if(c!='-'&&c!='+'&&(c<'0'||c>'9')) return 0;

        int len=str.length();
        int ans=0,tmp=0;
        int sign=1;
        int i=0;
        if(str.charAt(i)=='-'){
            sign=0;
        }
        if(str.charAt(i)=='-'||str.charAt(i)=='+'){
            ++i;       
        }

        while(i<len&&str.charAt(i)>='0'&&str.charAt(i)<='9'){
            tmp=str.charAt(i)-48;
            if(sign==1 && (ans>214748364 ||ans==214748364 && tmp>=7)){
                return 2147483647;
            }
            if(sign==0 && (ans>214748364 ||ans==214748364 && tmp>=8)){
            //注意符号是符数但ans不是负数
                return -2147483648;
            }
            ans=ans*10+tmp;
            ++i;
        }
        if(sign==0) return -ans;
        return ans;
    }
}

求和

题目描述

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

解题思路

递归或者与或运算

class Solution {
    public int sumNums(int n) {
        return n==0 ? 0:n+sumNums(n-1);
    }
}
/*
class Solution {
    public int sumNums(int n) {
        boolean flag = n > 0 && (n += sumNums(n - 1)) > 0;
        return n;
    }
}
*/

股票的最大利润

题目描述

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例:输入: [7,1,5,3,6,4]
输出: 5

解题思路

动态规划,dp[0]=0,cost不断更新最低价格

dp[i]=max(dp[i−1],prices[i]−min(cost,prices[i])。

class Solution {
    public int maxProfit(int[] prices) {
        int cost=Integer.MAX_VALUE,profit=0;
        for(int price:prices){
            cost=Math.min(cost,price);
            profit=Math.max(profit,price-cost);
        }
        return profit;
    }
}

连续子数组的最大和

题目描述

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。

解题思路

动态规划:由于 dp[i] 只与 dp[i-1] 和 nums[i] 有关系,因此可以将原数组 nums 用作 dp 列表,即直接在 nums 上修改即可。减少了O(n)的空间复杂度。
由于省去 dp 列表使用的额外空间,因此空间复杂度从 O(N) 降至 O(1) 。

class Solution {
    public int maxSubArray(int[] nums) {
        int result=nums[0];
        for(int i=1;i<nums.length;i++){
            nums[i]+=Math.max(nums[i-1],0);
            result=Math.max(nums[i],result);
        }
        return result;
    }
}

二叉树的最近公共祖先

题目描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

解题思路

二叉树本身就是递归定义的一种数据结构,使用递归解答本题还是很容易的。

解析:后序遍历DFS

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //递归出口
        if(root == null||root == p||root==q) return root;
        // 后续遍历,二叉树数的套路
        TreeNode l = lowestCommonAncestor(root.left, p, q);
        TreeNode r = lowestCommonAncestor(root.right, p, q);
        //边界条件
        if(l==null&&r==null) return null;
        if(l!=null&&r!=null) return root;
        if(l!=null&&r==null) return l;
        return r;
    }
}

从上到下打印二叉树

题目描述

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

解题思路

此题树刚好获取上一层,才打印下一层,层序遍历使用队列先进先出

1
1.2 1.1

第一层队列——————1

第二层队列———1.2,1.1,1(已经出队列)

队列存储一层一层的节点信息,然后使用list数组保存。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root==null) return new int[]{};//不判断会出错

        Queue<TreeNode> queue=new LinkedList<>();
        ArrayList<Integer> list=new ArrayList<>();
        queue.add(root);

        while(!queue.isEmpty()){
            //queue取出一个元素,还要把左右孩子加入队列
            TreeNode node=queue.remove();
            list.add(node.val);
            if(node.left!=null) queue.add(node.left);
            if(node.right!=null) queue.add(node.right);
        }

        int[] res=new int[list.size()];
        for(int i=0;i<res.length;i++){
            res[i]=list.get(i);
        }
        return res;
    }
}

从上到下打印二叉树 II

题目描述

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

算法流程:

  1. 特例处理: 当根节点为空,则返回空列表 [] ;
  2. 初始化: 打印结果列表 res = [] ,包含根节点的队列 queue = [root] ;
  3. BFS 循环: 当队列 queue 为空时跳出;
    • 新建一个临时列表 tmp ,用于存储当前层打印结果;
    • 当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度);
      • 出队: 队首元素出队,记为 node;
      • 打印: 将 node.val 添加至 tmp 尾部;
      • 添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ;
    • 将当前层结果 tmp 添加入 res 。
  4. 返回值: 返回打印结果列表 res 即可。

解题思路

大部分tree的题目
ProOrder先序遍历和postOrder后序遍历->DFS

少部分1:inOrder中序遍历->二叉搜索树

少部分2:leverOrder层序遍历->利用数据结构:队列实现

队列 先进先出 适合层序遍历,父节点在队列中,然后下一层子节点才能进队列

解决方案:从queue出去一个节点的时候,把左右节点加入queue,出来的节点数值使用一个Arraylist保存,最后转为int[]返回;

当每层单独输出一行,只需最开始记录每层的queue.size大小,然后每层输出即可

当交叉输出,把原来的Arraylist的尾部追加插入改为尾部插入/头部插入交叉即可,使用Linkedlist

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;

    //两种数据结构: Queue +  ArrayList
    Queue<TreeNode> queue = new LinkedList<>();
    //ArrayList list = new ArrayList<>();

    //核心方案: queue中取出一个元素, 再把其左右孩子加入queue
    queue.add(root);

    while(!queue.isEmpty()){
        ArrayList<Integer> temp = new ArrayList<>();
        int size = queue.size(); //一定要先获得, 防止fast fail

        for(int i = 0; i < size; i++){
            TreeNode node = queue.remove(); //queue中取出一个节点

            temp.add(node.val); //把节点值加入list
            if(node.left != null)   queue.add(node.left); //左孩子不空, 加入queue
            if(node.right != null)  queue.add(node.right);
        }
        res.add(temp);
    }

    return res;
}
}

讲解视频: Queue + ArrayList

从上到下打印二叉树 III

题目描述

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

解题思路

和上一个差不多,多了个头插

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res=new ArrayList<>();
        if(root==null) return res;

        Queue<TreeNode> queue=new LinkedList<>();
        ArrayList<Integer> list=new ArrayList<>();

        queue.add(root);
        boolean flag=true;
        while(!queue.isEmpty()){
        List<Integer> temp=new LinkedList<>();//头插效率更高
        int size=queue.size();
        for(int i=0;i<size;i++){
            TreeNode node=queue.remove();
            
            if(flag) temp.add(node.val);
            else temp.add(0,node.val);

            if(node.left!=null) queue.add(node.left);
            if(node.right!=null) queue.add(node.right);
        }
        flag=!flag;
        res.add(temp);
        }
        return res;
    }
}

二叉搜索树的第k大节点

题目描述

给定一棵二叉搜索树,请找出其中第k大的节点。

解题思路

二叉搜索树:

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 任意节点的左、右子树也分别为二叉查找树;
  • 没有键值相等的节点。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private int ans=0,count=0;
    public int kthLargest(TreeNode root, int k) {
        dfs(root,k);
        return ans;
    }
    public void dfs(TreeNode root,int k){
        if(root==null) return ;
        dfs(root.right,k);
        if(++count==k){
            ans=root.val;
        }
        dfs(root.left,k);
    }
}

二叉树的深度

题目描述

输入一棵二叉树的根节点,求该树的深度。

解题思路

此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于左子树的深度与右子树的深度 中的 最大值 +1。

/**
 * 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) {
        if(root==null) return 0;
        return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
    }
}

平衡二叉树

题目描述

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

解题思路

先写二叉树的深度函数,对于本题,使用DFS,不断判断左右子树是否满足条件,最后返回根节点的左右节点是否满足深度条件

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root==null) return true;
        int leftHeight=getHeight(root.left);
        int rightHeight=getHeight(root.right);
        if(Math.abs(rightHeight-leftHeight)>1) return false;
        return isBalanced(root.left)&&isBalanced(root.right);
    }
    public int getHeight(TreeNode root){
        if(root==null) return 0;
        int leftHeight=getHeight(root.left);
        int rightHeight=getHeight(root.right);
        return Math.max(leftHeight,rightHeight)+1;
    }
}

顺时针打印矩阵

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

解题思路

解析:题目解析

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length == 0) return new int[0];
        int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
        int[] res = new int[(r + 1) * (b + 1)];
        while(true) {
            for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.
            if(++t > b) break;
            for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
            if(l > --r) break;
            for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
            if(t > --b) break;
            for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
            if(++l > r) break;
        }
        return res;
    }
}
/*
import java.util.ArrayList;
public class Solution {
    public ArrayList printMatrix(int [][] matrix) {
        ArrayList list = new ArrayList();
        //定义四个边界
        int low = 0;
        int high = matrix.length -1;
        int left = 0;
        int right = matrix[0].length -1;

        while(low <= high && left <= right){
            for(int i = left; i <= right; i++){
                list.add(matrix[low][i]);
            }
            for(int i = low+1; i <= high; i++){
                list.add(matrix[i][right]);
            }
            if(low < high){
               for(int i = right-1; i >= left; i--){
                    list.add(matrix[high][i]);
                } 
            }
            if(left < right){
                for(int i = high-1; i >= low+1; i--){
                    list.add(matrix[i][left]);
                }
            }
            low++;
            high--;
            left++;
            right--;
        }
        return list;

    }
}
*/

序列化二叉树

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树。

解题思路

java 递归解法

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
public String serialize(TreeNode root) {
    if(root == null){
        return "null,";
    }
    String res = root.val + ",";
    res += serialize(root.left);
    res += serialize(root.right);
    return res;
}

// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
    String[] arr = data.split(",");
    Queue<String> queue = new LinkedList<String>();
    for(int i = 0; i < arr.length; i++){
        queue.offer(arr[i]);
    }
    return help(queue);
}
public TreeNode help(Queue<String> queue){
    String val = queue.poll();
    if(val.equals("null")){
        return null;
    }
    TreeNode root = new TreeNode(Integer.valueOf(val));
    root.left = help(queue);
    root.right = help(queue);
    return root;
}
}

其他解法:b站解析

public static String serialize(TreeNode root) {
        StringBuilder ans = new StringBuilder("[");
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int sum = 1; // 用来记录当前节点及其后面非空节点的个数
        while (!queue.isEmpty() && root != null) {
            TreeNode node = queue.poll();
            if (node == null) {
                ans.append("null");
            } else {
                ans.append(node.val);
                sum--;
                if (node.left != null) {
                    sum++;
                }
                if (node.right != null) {
                    sum++;
                }
                queue.add(node.left);
                queue.add(node.right);
            }
            if (sum != 0) {
                ans.append(",");
            } else {
                break;
            }
        }
        ans.append("]");
        return ans.toString();
    }

    // Decodes your encoded data to tree.
    public static TreeNode deserialize(String data) {
        String s = data.substring(1, data.length() - 1);
        if ("".equals(s)) {
            return null; // data = "[]"
        }
        String[] a = s.split(",");
        int index = 0;
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode root = new TreeNode(change(a[index++]));
        queue.add(root);
        while (!queue.isEmpty() && index < a.length) {
            TreeNode node = queue.poll();
            if (!"null".equals(a[index])) {
                node.left = new TreeNode(change(a[index++]));
                queue.add(node.left);
            } else {
                index++;
            }
            if (index < a.length && !"null".equals(a[index])) {
                node.right = new TreeNode(change(a[index++]));
                queue.add(node.right);
            } else {
                index++;
            }
        }
        return root;
    }

    private static int change(String s) {
        int res = 0;
        int i = 0;
        int flag = 1;
        if (s.charAt(0) == '-') {
            i++;
            flag = -1;
        }
        for (; i < s.length(); i++) {
            res = res * 10 + s.charAt(i) - '0';
        }
        return res * flag;
    }

包含min函数的栈

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

解题思路

其实就是维护两个栈,首先第一个栈是普通的数据栈,跟我们平常的栈一样,存储入栈数据;第二个栈就是建立在第一个栈的基础之上,他是维护第一个栈,就是去维护第一个栈中元素的最小值。

class MinStack {
    private Stack<Integer> datastack=new Stack<>();
    private Stack<Integer> minstack=new Stack<>();
    /** initialize your data structure here. */
    public void push(int x) {
        datastack.push(x);

        if(minstack.isEmpty()||minstack.peek()>x){
            minstack.push(datastack.peek());
        }else {
            minstack.push(minstack.peek());
        }
    }
    //栈的获取栈顶元素pop函数为peek()
    public void pop() {
        if(!datastack.isEmpty()){
            datastack.pop();
        }
        if(!minstack.isEmpty()){
            minstack.pop();
        }
    }
    
    public int top() {
        return datastack.peek();
    }
    
    public int min() {
        return minstack.peek();
    }
    
}

其他解法:b站up主解析

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

题目描述

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

解题思路

双指针+哈希表

  • 哈希表 dis 统计: 指针 j 遍历字符 s ,哈希表统计字符 s[j] 最后一次出现的索引 。
  • 更新左指针 i : 根据上轮左指针 i 和 dic[s[j]] ,每轮更新左边界 i ,保证区间 [i + 1, j] 内无重复字符且最大。
    i=max(dic[s[j]],i)
  • 更新结果 res : 取上轮 res 和本轮双指针区间 [i + 1,j] 的宽度(即 j - i )中的最大值。
    res=max(res,j−i)
class Solution{
    public int lengthOfLongestSubstring(String s){
        Map<Character,Integer> map=new HashMap<>();
        int i=-1,res=0;
        for (int j=0;j<s.length();j++){
            if(map.containsKey(s.charAt(j)))
                i=Math.max(map.get(s.charAt(j)),i);//map里的内容与即将放入的j对比,更新i的值
            map.put(s.charAt(j),j);
            res=Math.max(res,j-i);//放入j,更新结果
        }
        return res;
    }
}

把数字翻译成字符串

题目描述

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

解题思路

动态规划问题,考虑bp[i]=bp[i-1]还是bp[i]=bp[i-1]+bp[i-2],
如果新加入的数字和前一个数字可以组合,就加上bp[i-2]。

class Solution {
    public int translateNum(int num) {
        String s=String.valueOf(num);
        int n=s.length();
        int[] bp=new int[n+1];
        bp[0]=1;
        bp[1]=1;
        for(int i=2;i<=n;i++){
            bp[i]=bp[i-1];
            String tmp=s.substring(i-2,i);
            if(tmp.compareTo("10")>=0&&tmp.compareTo("25")<=0){
                bp[i]+=bp[i-2];
            }
        }
        return bp[n];
    }
}

正则表达式匹配

题目描述

请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。

解题思路

s:abc
p:.bc或者abc
s[i]=p[i]||p[i]=’.’

有:

表示出现零次
s:bc
p:a
bc
保持s不变,p前两个元素去除

表示出现一次或者多次
s:aabc
p:a
bc
保持p不变,s不断减去一个元素

的两个路要全试一遍
比如出现s:
s:abb
p:a
abb这种情况

class Solution {
    public boolean isMatch(String s, String p) {
        if(p.length()==0){
            return s.length()==0;
        }
        boolean fristmatch=(s.length()!=0)&&(s.charAt(0)==p.charAt(0)||p.charAt(0)=='.');
        if(p.length()>=2&&p.charAt(1)=='*'){
            return isMatch(s,p.substring(2))||(fristmatch&&isMatch(s.substring(1),p));
        }
        else{
            return fristmatch&&isMatch(s.substring(1),p.substring(1));
        }
    }
}

其他解法:动态规划DP

视频解析

重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

解题思路

前序遍历的第一个节点是根节点,只要找到根节点在中序遍历中的位置,在根节点之前被访问的节点都位于左子树,在根节点之后被访问的节点都位于右子树,由此可知左子树和右子树分别有多少个节点。

/**
 * 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) {
        if(preorder==null||preorder.length==0){
            return null;
        }

        TreeNode root=new TreeNode(preorder[0]);
        int index=findIndex(preorder,inorder);

        //构建左子树,右子树
        root.left=buildTree(Arrays.copyOfRange(preorder,1,index+1),
        Arrays.copyOfRange(inorder,0,index));

        root.right=buildTree(Arrays.copyOfRange(preorder,index+1,preorder.length),
        Arrays.copyOfRange(inorder,index+1,inorder.length));

        return root;
    }
    public int findIndex(int[] preorder,int[] inorder){
        for(int i=0;i<inorder.length;i++){
            if(inorder[i]==preorder[0])
            return i;
        }
        return 0;
    }
}

解析:up主讲解

矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

示例:

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”

输出:true

解题思路

深搜+回溯,深搜的过程其实就是对四个方向的一个递归调用的过程,回溯的话是为了消除某一次递归调用所产生的路径不能匹配模式串所产生的影响要被消除掉,消除的结果就是对这条路径上的每一个位置进行状态初始化,即标记为未被遍历。

class Solution{
    public boolean exist(char[][] board, String word) {
        boolean[][] visit=new boolean[board.length][board[0].length];
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[i].length;j++){
                if(solve(board,word,i,j,visit,0)){
                    //找到一种情况即可
                    return true;
                }
            }
        }
        return false;
    }

    public boolean solve(char[][] board,String word,int x,int y,boolean[][] vis,int index){
        if(x<0||x>=board.length||y<0||y>=board[0].length||vis[x][y]){
            return false;
        }
        if(word.charAt(index)!=board[x][y]){
            return false;
        }
        if(index==word.length()-1){
            return true;
        }

        vis[x][y]=true;//位置标记
        boolean flag=solve(board,word,x+1,y,vis,index+1)||
                     solve(board,word,x-1,y,vis,index+1)||
                     solve(board,word,x,y+1,vis,index+1)||
                     solve(board,word,x,y-1,vis,index+1);
        vis[x][y]=false; //回溯,bfs关键步骤
        return flag;
    }
}

机器人的运动范围

题目描述

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

解题思路

可以剪枝,向左和向上的不用遍历,只用遍历向右和向下的。

class Solution {
    public int movingCount(int m, int n, int k) {
        boolean[][] visit=new boolean[m][n];
        return dfs(0,0,m,n,k,visit);
    }

    public int sum(int i,int j){
        int sum=0;
        while(i!=0){
            sum+=i%10;
            i/=10;
        }
        while(j!=0){
            sum+=j%10;
            j/=10;
        }
        return sum;
    }
    public int dfs(int i,int j,int m,int n,int k,boolean[][] vis){
        if(i<0||j<0||i>=m||j>=n||sum(i,j)>k||vis[i][j]){
            return 0;
        }
        vis[i][j]=true;
        return 1+dfs(i+1,j,m,n,k,vis)
        //+dfs(i-1,j,m,n,k,vis)
        +dfs(i,j+1,m,n,k,vis);
        //+dfs(i,j-1,m,n,k,vis);
    }
}

剪绳子

题目描述

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

解题思路

不难发现,将长度为n的绳子剪成m段。满足每段都是n/m时,乘积最大。但n/m可能不是整数。把剩下的left长度分给num,left段,即有left段长度为num+1,有m-left段长度为num。然后遍历段数从2到n找到最大值即可。

class Solution {
    public int cuttingRope(int n) {
        int ans=0;
        for(int i=2;i<=n;i++){
            ans=Math.max(ans,mylength(n,i));
        }
        return ans;
    }
    public int mylength(int n,int m){
        int num=n/m;
        int remain=n%m;
        return (int)(Math.pow(num,m-remain))*(int)(Math.pow(num+1,remain));
    }
}

剪绳子 II

题目描述

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m] 。请问 k[0]k[1]…*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

解题思路

5:2+3 6:3+3 >6的数可以切分为2或者3,优先考虑3(递推得出结论)

class Solution {
    public int cuttingRope(int n) {
        //if(n==2) return 1;
        //if(n==3) return 2;
        if(n<=3) return n-1;
        if(n==4) return 4;
        long ans=1; //一定是long类型
        while(n>4){
            ans*=3;
            ans%=1000000007;
            n-=3;
        }
        return (int)(ans*n%1000000007);
    }
}

表示数值的字符串

题目描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、“±5”、"-1E-16"及"12e+5.4"都不是。

解题思路

模拟题,练习意义不大,所以使用了库函数Double.parseDouble转换。

class Solution {
    public boolean isNumber(String s) {
        if (s.endsWith("f") || s.endsWith("d") || s.endsWith("F") || s.endsWith("D")) {
            return false;
        }
        try {
            Double.parseDouble(s);
        } catch (Exception e){
            return false;
        }
        return true;
    }
}
/**
public class Solution {
    public boolean isNumeric(char[] str) {
        // 标志小数点和指数
        boolean point = false, exp = false;

        for (int i = 0; i < str.length; i++) {
            if (str[i] == '+' || str[i] == '-') {
                // +-号后面必定为数字 或 后面为.(-.123 = -0.123)
                //如果没有下一位或者下一位部位数字或者下一位为小数点,直接返回
                if (i + 1 == str.length 
                    || !(str[i + 1] >= '0' && str[i + 1] <= '9' 
                         || str[i + 1] == '.')) { 
                    return false;
                }
                // +-号只出现在第一位或eE的后一位
                if (!(i == 0 || str[i-1] == 'e' || str[i-1] == 'E')) {
                    return false;
                }
            } else if (str[i] == '.') {
                // .后面必定为数字 或为最后一位(233. = 233.0)
                if (point || exp 
                    || !(i+1 < str.length && str[i+1] >= '0' && str[i+1] <= '9')) { 
                    return false;
                }
                point = true;
            } else if (str[i] == 'e' || str[i] == 'E') {
                // eE后面必定为数字或+-号
                if (exp || i + 1 == str.length 
                        || !(str[i + 1] >= '0' && str[i + 1] <= '9' 
                        || str[i + 1] == '+' 
                        || str[i + 1] == '-')) { 
                    return false;
                }
                exp = true;
            } else if (str[i] >= '0' && str[i] <= '9') {
                continue;
            } else {
                return false;
            }
        }
        return true;
    }
}
*/

数组中数字出现的次数

题目描述

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

解题思路

所有值异或出来的结果会有某一位数字为1,根据这个1所在的位置把数组分为两部分,两个不相等的数字肯定被分为两个不同组,然后分别异或就可以得到这个数字。

1<

class Solution {
    public int[] singleNumbers(int[] nums) {
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum^=nums[i];
        }
        int y=1;
        while((y&sum)==0){
            y=y<<1; //找到第y位异或为1的位置,y数字保存这个位置
        }
        int[] res=new int[2];
        for(int i=0;i<nums.length;i++){
            if((y&nums[i])==0){
                res[0]^=nums[i];
            }else{
                res[1]^=nums[i];
            }
        }
        return res;   
    }
}

数组中数字出现的次数 II

题目描述

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

解题思路

排序,有点偷懒的做法。

class Solution {
    public int singleNumber(int[] nums) {
        int len=nums.length;
        Arrays.sort(nums);
        if(len==1||nums[0]!=nums[1])  return nums[0];
        if(nums[len-2]!=nums[len-1])  return nums[len-1];
        for(int i=1;i<nums.length;i++){
            if(((nums[i]^nums[i-1])!=0)&&((nums[i]^nums[i+1])!=0)){
                return nums[i];
            }
        }
        return 0;
    }
}

其他解法:自动机+位运算

和为s的两个数字

题目描述

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

解题思路

递增数组,所以使用双指针

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left=0,right=nums.length-1;
        while(left<right){
            int sum=nums[left]+nums[right];
            if(sum==target){
                return new int[]{nums[left],nums[right]};
            }else if(sum>target){
                right--;
            }else{
                left++;
            }
        }
        return new int[]{};
    }
}

数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

解题思路

class Solution {
    private int sum; // 用来去统计逆序对的个数
    public int reversePairs(int [] nums) {
        sum = 0;
        int l = 0;
        int r = nums.length - 1;
        divide(l ,r, nums);
        return  sum;
    }
    private void divide(int l, int r, int[] array) {
        if(l>=r){
            return ;
        }else{
            int mid = l+(r-l)/2;
            divide(l, mid, array);
            divide(mid + 1, r, array);
            merge(l, r, mid, array);
        }
    }
    private void merge(int l, int r, int mid, int[] array) {
        int i = l; // 左区间的起点
        int j = mid + 1; // 右区间的起点
        int[] temp = new int[r - l + 1];
        int index = 0;
        while (i <= mid && j <= r) {
            if (array[i] > array[j]) {
                temp[index++] = array[j++];
                sum += mid - i + 1; // 这一行是核心,去统计逆序对个数
            } else {
                temp[index++] = array[i++];
            }
        }
        while (i <= mid) {
            temp[index++] = array[i++];
        }
        while (j <= r) {
            temp[index++] = array[j++];
        }
        index = 0;
        for (int k = l; k <= r; k++) {
            array[k] = temp[index++];
        }
        //System.arraycopy(temp,0,array,0,r-l+1); 将temp数组里从索引为0的元素开始, 复制到数组array里的索引为0的位置, 复制的元素个数为length个. 
    }
}

算法讲解:b站up主解析

队列的最大值

题目描述

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1。

解题思路

使用Deque记录队列最大值,当一个元素加进队列中把之前比deque中比它小的最大记录值去除;当一个元素出队列判断该元素是否为最大值,是的话更新Queue中的值。

class MaxQueue {
    Queue<Integer> queue;
    Deque<Integer> deque;

    public MaxQueue() {
        queue=new LinkedList<>();
        deque=new LinkedList<>();
    }
    
    public int max_value() {
        if(deque.isEmpty()) return -1;
        return deque.peek();
    }
    
    public void push_back(int value) {
        queue.add(value);
        while(!deque.isEmpty()&&deque.getLast()<value){
            deque.removeLast();
        }
        deque.add(value);
    }
    
    public int pop_front() {
        if(queue.isEmpty()) return -1;
        int ans=queue.poll();
        if(ans==deque.peek()){
            deque.poll();
        }
        return ans;
    }
}

题目解析: up主xmmmm

在排序数组中查找数字 I

题目描述

统计一个数字在排序数组中出现的次数。

解题思路

对于排序过的数组使用二分,水题

class Solution {
    public int search(int[] nums, int target) {
        int left=0,right=nums.length-1;
        int count=0;
        while(left<right){
            int mid=left+(right-left)/2;
            if(nums[mid]>=target){
                right=mid;
            }
            if(nums[mid]<target){
                left=mid+1;
            }
        }
        while(left<nums.length&&nums[left++]==target){
            count++;
        }
        return count;
    }
}

0~n-1中缺失的数字

题目描述

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

解题思路

递增数组查找数字,二分。

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

和为s的连续正数序列

题目描述

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

解题思路

这道题当前可以用等差数列的求和公式来计算滑动窗口的和。不过我这里没有使用求和公式,是为了展示更通用的解题思路。实际上,把题目中的正整数序列换成任意的递增整数序列,这个方法都可以解。

class Solution {
    public int[][] findContinuousSequence(int target) {
        int i=1,j=1;//双指针(滑动窗口)初始都为1
        int sum=0;
        List<int[]> res=new ArrayList<>();
        while(i<=target/2){
            if(sum<target){
                sum+=j;
                j++;
            }else if(sum>target){
                sum-=i;
                i++;
            }else{
                int[] arr=new int[j-i];
                for(int k=i;k<j;k++){
                    arr[k-i]=k;
                }
                res.add(arr);
                sum-=i;
                i++;
            }
        }
    return res.toArray(new int[res.size()][]);
    }
}

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

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

解题思路

双指针做法,实际思想是快排.

class Solution {
    public int[] exchange(int[] nums) {
        int left =0;
        int right=nums.length-1;
        while(left<right){
            while(left<right&&nums[left]%2==1){
                left++;
            }
            while(left<right&&nums[right]%2==0){
                right--;
            }
            if(left<right){
                int temp=nums[left];
                nums[left]=nums[right];
                nums[right]=temp;
            }
        }
        return nums;
    }
}

丑数

题目描述

我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

解题思路

假设当前存在 3 个数组 nums2, nums3, nums5 分别代表丑数序列从 1 开始分别乘以 2, 3, 5 的序列, 即

nums2 = {12, 22, 32, 42, 52, 62,…}

nums3 = {13, 23, 33, 43, 53, 63,…}

nums5 = {15, 25, 35, 45, 55, 65,…}

那么, 最终的丑数序列实际上就是这 3 个有序序列对的合并结果, 计算丑数序列也就是相当于 合并 3 个有序序列。

class Solution {
    public int nthUglyNumber(int n) {
        int[] a = new int[n];
        a[0] = 1;
        int index1 = 0; // 遍历丑数*2的队列
        int index2 = 0; // 遍历丑数*3的队列
        int index3 = 0; // 遍历丑数*5的队列

        for (int i = 1; i < n; i++) {
            a[i]=Math.min(a[index1]*2,Math.min(a[index2]*3,a[index3]*5));
            if(a[i]==a[index1]*2){
                index1++;
            }
            if(a[i]==a[index2]*3){
                index2++;
            }
            if(a[i]==a[index3]*5){
                index3++;
            }
        }
        return a[n-1];
    }
}

礼物的最大价值

题目描述

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

解题思路

f(i, j) = max{f(i - 1, j), f(i, j - 1)} + grid[i][j].

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 && j == 0) continue;
                if(i == 0) grid[i][j] += grid[i][j - 1] ;
                else if(j == 0) grid[i][j] += grid[i - 1][j];
                else grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
            }
        }
        return grid[m - 1][n - 1];
    }
}

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for(int j = 1; j < n; j++) // 初始化第一行
            grid[0][j] += grid[0][j - 1];
        for(int i = 1; i < m; i++) // 初始化第一列
            grid[i][0] += grid[i - 1][0];
        for(int i = 1; i < m; i++)
            for(int j = 1; j < n; j++) 
                grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
        return grid[m - 1][n - 1];
    }
}

/**
多开一行一列的空间能够让代码更简洁
class Solution {
    public int maxValue(int[][] grid) {
        int row=grid.length,col=grid[0].length;
        int[][] dp=new int[row+1][col+1];
        for(int i=1;i<=row;i++){
            for(int j=1;j<=col;j++){
                dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
            }
        }
        return dp[row][col];
    }
}
*/

链表中倒数第k个节点

题目描述

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

解题思路

快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode frontNode=head;
        ListNode behindNode=head;
        while(frontNode!=null&&k>0){
            frontNode=frontNode.next;
            k--;
        }
        while(frontNode!=null){
            frontNode=frontNode.next;
            behindNode=behindNode.next;
        }
        return behindNode;
    }
}

反转链表

题目描述

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

解题思路

  • 定义两个指针: pre 和 cur ;pre 在前 cur 在后。
  • 每次让 pre 的 next 指向 cur ,实现一次局部反转
  • 局部反转完成之后, pre 和 cur 同时往前移动一个位置
  • 循环上述过程,直至 pre 到达链表尾部.
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur=null;
        ListNode pre=head;
        ListNode tmp=null;
        while(pre!=null){
            tmp=pre.next;
            pre.next=cur;
            cur=pre;
            pre=tmp;
        }
        return cur;
    }
}

其他解法:递归

树的子结构

题目描述

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构).B是A的子结构, 即 A中有出现和B相同的结构和节点值。

解题思路

首先在isSubStructure()函数中进行判断:

  • (1)利用fun判断以根节点为开始的结构是否为真;
  • (2)若不为真,同样利用fun对其左子树判断;
  • (3)都不为真,再对其右子树判断。
    只要三者有一个为真,则结果为真。

以上是主体部分,接下来再对fun进行分析:
fun是一个工具函数,用来判断当前结构与B是否完全相同,所以必须满足三个条件:

  • (1)首先当前节点值必须与B的节点值相同;
  • (2)当前节点的左子树结构必须与B的左子树结构完全相同(递归调用fun工具函数);
  • (3)当前节点的右子树结构必须与B的右子树结构完全相同(递归调用fun工具函数);

接下来分析边界条件:

  • 对于isSubStructure()函数中,如果刚开始传进来的A,B只要有一个为空,则结果为false;
  • 对于fun()函数,在比较过程中,如果B比完了,则说明存在该子结构,如果B还有节点,而A没有了,则说明A中不存在该子结构,返回false。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if(A==null||B==null){
            return false;
        }
        return dfs(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
    }

    public boolean dfs(TreeNode a,TreeNode b){
        if(b==null) return true; //必须先判断b边界条件
        if(a==null) return false;
        return (a.val==b.val)&&dfs(a.left,b.left)&&dfs(a.right,b.right);
    }
}

合并两个排序的链表

题目描述

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

解题思路

递归解法,非递归就按正常思路往cur指针加数然后往后更新cur指针,最后把其中一个剩下的非空加入cur就行.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }
        if(l1.val<l2.val){
            l1.next=mergeTwoLists(l1.next,l2);
            return l1;
        }else {
            l2.next=mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

解析

二叉树的镜像

题目描述

请完成一个函数,输入一个二叉树,该函数输出它的镜像。

解题思路

左右子树调换,使用递归简单解答,边界条件为root为空.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root==null){
            return null;
        }
        TreeNode tmp=root.left;
        root.left=mirrorTree(root.right);
        root.right=mirrorTree(tmp);
        return root;
    }
}

对称的二叉树

题目描述

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

解题思路

对称二叉树定义: 对于树中 任意两个对称节点 L 和 R ,一定有:

  • L.val = R.val :即此两对称节点值相等。
  • L.left.val = R.right.val :即 L 的 左子节点 和 R 的 右子节点 对称;
  • L.right.val = R.left.val :即 L 的 右子节点 和 R 的 左子节点 对称。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null){
            return true;
        }
        return help(root.left,root.right);
    }
    public boolean help(TreeNode l,TreeNode r){
        if(l==null&&r==null){
            return true;
        }
        if(l==null||r==null||l.val!=r.val){
            return false;
        }
        return help(l.left,r.right)&&help(l.right,r.left);
    }
}

滑动窗口的最大值

题目描述

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

解题思路

队列尾部进头部出

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n=nums.length;
        if(n==0){
            return nums;
        }
        int[] res=new int[n-k+1];
        //dq里面存的是数组的index, 不是数组的值
        Deque<Integer> deque=new LinkedList<>();

        for(int i=0;i<n;i++){
            //移除头部,保证窗口长度
            if(!deque.isEmpty()&&deque.getFirst()<(i-k+1)){
                deque.removeFirst();
            }
            //移除尾部比当前要加入的值小的元素(不可能答案)
            while(!deque.isEmpty()&&nums[i]>=nums[deque.getLast()]){
                deque.removeLast();
            }
            //尾部加入,窗口滑动
            deque.addLast(i);
            //保证滑动窗口长度,并记录最大值
            if(i>=k-1){
                res[i-k+1]=nums[deque.getFirst()];
            }
        }
        return res;
    }
}

解题思路:up主xmmmmmm

n个骰子的点数

题目描述

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

解题思路

嗯这道题算是简单题我晕了,ac后以后再看吧

class Solution {
    public double[] twoSum(int n) {
        int[][] dp=new int[n+1][n*6+1];
        //一个骰子能摇出点数的次数
        for(int i=1;i<=6;i++)
            dp[1][i]=1;
        for(int i=1;i<=n;i++)//骰子个数
            for(int j=i;j<=6*i;j++)//i个骰子能要出的点数
                for(int k=1;k<=6;k++){//第i个骰子要出的点数
                    if(j<k)break;
                    dp[i][j]+=dp[i-1][j-k];//前i-1个骰子摇出的点数加上本次摇出的点数
                }
    
                

        double total=Math.pow(6,n);
        double[] res=new  double[5*n+1];
        int ind=0;
        for(int i=n;i<=n*6;i++){
            res[ind++]=dp[n][i]/total;
        }
            
        return res;
    }
}

栈的压入、弹出序列

题目描述

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

解题思路

把压栈的元素按顺序压入,当栈顶元素和出栈的第一个元素相同,则将该元素弹出,出栈列表指针后移并继续判断。最后判断出栈列表指针是否指向出栈列表的末尾即可。

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
      Stack<Integer> stack=new Stack<>();
      int i=0;
      for(int num:pushed){
          stack.push(num);
          while(!stack.isEmpty()&&stack.peek()==popped[i]){
              stack.pop();
              i++;
          }
      }
      return stack.isEmpty();
    }
}

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

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

解题思路

对于二叉搜索树,后序遍历

思路就是左节点-右节点-跟节点,然后找到左右子树边界判断根节点的右子树是否满足二叉搜索树.

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        if(postorder.length==0){
            return true;
        }
        return dfs(postorder,0,postorder.length-1);
    }

    public boolean dfs(int[] postorder,int l,int r){
        if(l>r){
            return true; // 当前区域不合法的时候直接返回true就好
        }
        int root=postorder[r]; // 当前树的根节点的值
        int k=0;
        while(k<r&&postorder[k]<root){  //找到右节点
            k++;
        }
        for(int i=k;i<r;i++){
            if(postorder[i]<root){
                return false;
            }
        }
        return dfs(postorder,l,k-1)&&dfs(postorder,k,r-1);
    }
}

解析

1~n整数中1出现的次数

题目描述

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

解题思路

暴力解决,超时QAQ,不管了,先贴一个大佬的方案:

求高位以及不断乘10取当前位的i需要用long表示,因为取一些很大的int的高位就溢出了.

/**
class Solution {
    public int countDigitOne(int n) {
        int sum=0;
        for(int i=1;i<=n;i++){
            int x=i;
            while(x!=0){
                if(x%10==1){
                    sum++;
                }
                x/=10;
            }
        }
        return sum;
    }
}
*/

class Solution {
    public int countDigitOne(int n) {
        int count = 0;
	long i = 1;        // 从个位开始遍历到最高位
	while(n / i != 0) {
		long high = n / (10 * i);  // 高位
		long cur = (n / i) % 10;   // 当前位
		long low = n - (n / i) * i;
		if(cur == 0) {
			count += high * i;
		}else if(cur == 1) {
			count += high * i + (low + 1);
		}else {
			count += (high + 1) * i;
		}
		i = i * 10;
	}
	return count;
    }
}

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

题目描述

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

解题思路

根节点遍历每条路径,符合的加入,然后递归遍历左右子树.

class Solution {
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<>(); 
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        recur(root, sum);
        return res;
    }
    void recur(TreeNode root, int tar) {
        if(root == null) return;
        path.add(root.val);
        tar -= root.val;
        if(tar == 0 && root.left == null && root.right == null)
            res.add(new LinkedList(path));
        recur(root.left, tar);
        recur(root.right, tar);
        path.removeLast();
    }
}

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    List<List<Integer>> res;

    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        res = new ArrayList<>();
        dfs(root, sum, 0, new LinkedList<>());
        return res;
    }

    private void dfs(TreeNode root, int sum, int count, LinkedList<Integer> list) {
        if (root == null) {
            return;
        }
        count += root.val;
        list.add(root.val);
        if (root.left == null && root.right == null) {
            if (count == sum) {
                res.add(new LinkedList<>(list));
            }
        }
        dfs(root.left, sum, count, list);
        dfs(root.right, sum, count, list);
        list.removeLast();
    }
}

二叉搜索树与双向链表

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

解题思路

用一个全局变量来记录遍历过程的上一个节点。

还是利用二叉搜索树的特点遍历 中序遍历

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    Node pre = null, head = null;
    public Node treeToDoublyList(Node root) {
        
        if (root == null)
            return null;
        
        helper(root);
        head.left = pre;
        pre.right = head;

        return head;
    }

    public void helper(Node root) {

        if (root == null)
            return;
        
        helper(root.left);

        root.left = pre;
        
        if (pre != null)
            pre.right = root;
        else
            head = root;

        pre = root;
        helper(root.right);
    }
}

把数组排成最小的数

题目描述

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

解题思路

通过在排序时传入一个自定义的 Comparator 实现,重新定义 String 列表内的排序方法,若拼接 s1 + s2 > s2 + s1,那么显然应该把 s2 在拼接时放在前面,以此类推,将整个 String 列表排序后再拼接起来。

class Solution {
    public String minNumber(int[] nums) {
        if(nums==null||nums.length==0){
            return null;
        }

        //转化为String类型数组,
        String[] s=new String[nums.length];
        for(int i=0;i<nums.length;i++){
            s[i]=String.valueOf(nums[i]);
        }

        //对其排序,这里使用了java1.8新特性Lambda 表达式,指向性符号表示进入了方法体内部,这里直接返回了语句
        Arrays.sort(s,(s1,s2) -> ((s1+s2).compareTo(s2+s1)) );

        //由于要求返回String类型,这里使用stringbuilder连接
        StringBuilder ans=new StringBuilder();
        for(String i:s){
            ans.append(i);
        }
        return ans.toString();
    }
}

解析

Lambda 表达式介绍

数字序列中某一位的数字

题目描述

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

解题思路

首先,我们要明确的是,n是下标,从0开始的!

我们可以注意到规律 09有10个数字,1099有90个数字,100999有900个数字,so

class Solution {
    public int findNthDigit(int n) {
        if (n<10)
            return n;
        int i = 1;
        while (n>i*(Math.pow(10,i-1))*9){   //循环结束后,i-1就是位数,n-1为表示还要找多少个
            n -= i*Math.pow(10,i-1)*9;
            i++;
        }
        char[] result = String.valueOf((int) Math.pow(10,i-1) + (n-1) / i).toCharArray();//我们用字符串来接收值,方便找位数 result结果为我们要的那个数的
        int value = result[(n-1)%i]-'0';    //(n-1)%位数 得出我们要的第x位的数
        return value;
    }
}

还没看的题解

数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

解题思路

大顶堆 小顶堆 做的时候直接copy别人的代码,以后有时间再看QAQ

把数据分为两部分,让左半部分永远元素个数永远大于等于右半部分,这样左端大顶堆的堆顶就是左半部分最大值,右端小顶堆的堆顶就是右半部分最小值。

class MedianFinder {
    Queue<Integer> A, B;
    public MedianFinder() {
        A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
        B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
    }
    public void addNum(int num) {
        if(A.size() != B.size()) {
            A.add(num);
            B.add(A.poll());
        } else {
            B.add(num);
            A.add(B.poll());
        }
    }
    public double findMedian() {
        return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
    }
}

题目解析

视频讲解

构建乘积数组

题目描述

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

解题思路

对称遍历

  • 从左往右遍历累乘,结果保存在数组 res 中,此时 res[i] 表示,A[i] 左边所有元素的乘积
  • 然后从右往左遍历累乘,获取A[i] 右边所有元素的乘积
  • 两边遍历之后得到的 res,就是最终结果
class Solution {
    public int[] constructArr(int[] a) {
        int n=a.length;
        int[] ans=new int[n];
        for(int i=0,cur=1;i<n;i++){
            ans[i]=cur;  //先乘左边的数,不包括自己
            cur*=a[i];
        }
        for(int i=n-1,cur=1;i>=0;i--){
            ans[i]*=cur;  //再乘右边的数,不包括自己
            cur*=a[i];
        }
        return ans;
    }
}

不用加减乘除做加法

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

解题思路

不用加减乘除做加法的方法是使用按位异或和按位与运算。计算a + b等价于计算(a ^ b) + ((a & b) << 1),其中((a & b) << 1)表示进位。因此令a等于(a & b) << 1,令b等于a ^ b,直到a变成0,然后返回b。

class Solution {
    public int add(int a, int b) {
        while(a!=0){
            int temp=a^b;
            a=(a&b)<<1;
            b=temp;
        }
        return b;
    }
}

扑克牌中的顺子

题目描述

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

解题思路

根据题意,此 5 张牌是顺子的 充分条件 如下:

  • 除大小王外,所有牌 无重复 ;
  • 设此 5 张牌中最大的牌为 max ,最小的牌为 min (大小王除外),则需满足:max - min < 5
class Solution {
    public boolean isStraight(int[] nums) {
        Set<Integer> set=new HashSet<>();
        int max=0,min=14;
        for(int num:nums){
            if(num==0){
                continue;
            }
            max=Math.max(max,num);
            min=Math.min(min,num);
            if(set.contains(num)){
                return false;
            }
            set.add(num);
        }
        return max-min<5;
    }
}

圆圈中最后剩下的数字

题目描述

0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

解题思路

假设当前删除的位置是 idx,下一个删除的数字的位置是 idx + m 。但是,由于把当前位置的数字删除了,后面的数字会前移一位,所以实际的下一个位置是 idx + m - 1。由于数到末尾会从头继续数,所以最后取模一下,就是 (idx + m - 1) (mod n)。

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

解析

你可能感兴趣的:(数据结构与算法,剑指offer)