剑指offer刷题


title: 剑指offer刷题
date: 2018-09-21 17:32:47
updated: 2020-02-22 14:21:33
categories: 算法刷题
tags:
- 算法刷题


此文档包含剑指offer书上所有题目,部分简单的题目没有总结。其中涉及leetcode相关题目也进行对比记录,尽量都留存了最优解。同时牛客网上有剑指offer专题oj,可在上面进行练习验证。

一、题目总览

编号题目
1找出数组中重复的数字
2不修改数组找出重复的数字
30~n-1中缺失的数字(2道)
4数组中数字出现的次数(3道)
5数组中出现次数超过一半的数字
6数组中出现次数超过1/3的数字
7统计指定数字在排序数组中出现的次数
8二维数组中的查找
9替换空格
10Replace Words–字典树
11Find And Replace in String
12从尾到头打印链表
13反转链表(三道)
14重建二叉树
15二叉树的下一个节点
16Populating Next Right Pointers in Each Node
17Populating Next Right Pointers in Each Node II
18两个栈实现队列
19两个队列实现栈
20斐波那契数列(三道)
21斐波那契数列变种_medium难度(两道)
22Additive Number
23旋转数组的最小数字(三道)
24矩阵中的路径
25机器人的运动范围
26Unique Paths(两道)
27剪绳子
28不用加减乘除做加法(位运算)
29Two Sum
30二进制中1的个数
31数值的整数次方
32打印从1到最大的n位数
33删除链表中重复的节点(两道)
34正则表达式匹配
35表示数值的字符串
36调整数组顺序使奇数位于偶数前面(3道)
37链表中倒数第k个结点(两道)
38判断链表是否有环(两道)
39两个单链表相交的第一个公共节点(2道)
40合并两个排序的链表
41树的子结构
42二叉树的翻转(镜像)与对称(2道)
43顺时针打印矩阵
44包含min函数的栈
45栈的压入、弹出序列
46二叉树的层序遍历(3道)
47递归与非递归实现二叉树前序、中序、后序遍历
48判断数组是否为二叉搜索树的后续遍历序列
49二叉树中和为某一值的路径
50复杂链表的复制
51二叉搜索树与双向链表(两道)
52序列化二叉树
53全排列问题(五道)
54打印出给定字符串中字符的所有组合
55大/小根堆–优先队列实现(两道)
56数据流中的中位数
57数组中最大连续子序列的和
581到n整数中1出现的次数
59正整数序列中的第n个数字
60把数组排成最小的数
61求把一个数字翻译成不同字符串的个数(DP)
62礼物的最大价值
63最长不含重复的子字符串长度
64丑数(两道)
65第一个只出现一次的字符
66归并排序相关(3道)
67二叉搜索树的第k小节点(中序遍历)
68二叉树的深度(两道)
69数组中和为s的数字(四道)
70翻转字符串(两道)
71滑动窗口的最大值(双端队列)
72打印n个骰子所有可能的点数和及概率
73扑克牌中的顺子(2道)
74圆圈中剩下的数(约瑟夫环问题)
75股票的最大利润问题(四道)
76求1+2+3+…+n(不用if而用或短路的方式结束递归)
77构建乘积数组
78普通二叉树中两个节点的最低公共祖先
79把字符串转换成整数

二、题目及解答

1.找出数组中重复的数字

LeetCode 217:Contains Duplicate 
LeetCode 219:Contains Duplicate II 
LeetCode 220:Contains Duplicate III 
LeetCode 287:Find the Duplicate Number

1.《剑指offer》p39、leetcode 287

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

思路:交换法、映射。数组不重复的情形是array[i]=i。遍历数组元素,元素值为m时,与array[m]的值作对比,如果相同则重复了,返回即可,否则二者交换,继续比较,直到遍历完数组。时间o(n),空间o(1)

public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        for(int i=0; i<length; ++i){
            while(numbers[i]!=i){//注意这里是while
                if(numbers[numbers[i]]==numbers[i]){
                    duplication[0] = numbers[i];
                    return true;
                }else{
                    swap(numbers, i, numbers[i]);
                }
            }
        }
        return false;
    }
	
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

2.LeetCode 217:Contains Duplicate

题目:Given an array of integers, find if the array contains any duplicates.

Your function should return true if any value appears at least twice in the array, and it should return false if every element is distinct.

Input: [1,2,3,1]
Output: true

思路:

1)将数组排序,判断前后两个元素是否一样,如果一样则返回true,否则返回false;

2)方法(1)对数组进行排序,改变了数组元素的位置;若要求不能修改数组元素,可以创建一个辅助HashSet,判断HashSet中是否已经存在该元素,存在则返回true,否则返回false,并将其加入在HashSet中。

//法一:排序(改变了位置,时间o(nlogn),空间o(1))
public boolean containsDuplicate(int[] nums) {
    Arrays.sort(nums);
    for(int i=0; i<nums.length-1; ++i){
        if(nums[i+1]==nums[i]){
            return true;
        }
    }
    return false;
}
	
//法二:hashSet(不改变位置,时间o(n),空间o(n))
public boolean containsDuplicate2(int[] nums) {
    Set<Integer> set = new HashSet<>();
    for(int num: nums){
        if(!set.add(num)){
            return true;
        }
    }
    return false;
}

3.LeetCode 219:Contains Duplicate II

题目:给定一个数组nums和一个整数k,是否存在两个不相等的整数 i 和 j,使得nums[i] == nums[j],并且i和j之间的距离最多为k。

Input: nums = [1,2,3,1], k = 3
Output: true
	
Input: nums = [1,0,1,1], k = 1
Output: true
	
Input: nums = [1,2,3,1,2,3], k = 2
Output: false

思路:辅助map存储数组元素值-元素索引,遍历数组即可,时间o(n),空间o(n)

public boolean containsNearbyDuplicate(int[] nums, int k) {
    //值-索引
    Map<Integer, Integer> map = new HashMap<>();
    for(int i=0; i<nums.length; ++i){
        if(map.containsKey(nums[i]) && (i-map.get(nums[i]))<=k){//找到重复
            return true;
        }else{
            map.put(nums[i], i);//更新键值对
        }
    }
    return false;
}

4.LeetCode 220:Contains Duplicate III

题目:给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得nums [i]和nums [j]之间的绝对差值不超过为t,i和j之间的绝对差值不超过ķ。

思路:维持一个大小为k的窗口,由左向右在nums中移动。对于nums[i],只要查找其之前的元素中是否存在大小范围在[nums[i] - t,nums[i] + t]的元素,如果存在就返回true。还要注意整数的溢出问题,Long

//使得nums [i]和nums [j]之间的绝对差值不超过为t,i和j之间的绝对差值不超过ķ。
//思路:treeset滑窗保存有序元素
//treeset.ceiling--返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null
//treeset.floor--返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
    if (nums == null || nums.length == 0 || k <= 0){
        return false;
    }
    TreeSet<Long> treeSet = new TreeSet<>();
    for(int i=0; i<nums.length; ++i){
        if(i>k){//超过滑窗k,删除treeset中的一个元素
            treeSet.remove((long)nums[i-k-1]);
        }
        Long left = treeSet.ceiling((long)nums[i]-t);
        Long right = treeSet.floor((long)nums[i]+t);
        if(left!=null && right!=null && right>=left){
            return true;
        }
        treeSet.add((long)nums[i]);
    }
    return false;
}

2.不修改数组找出重复的数字

《剑指offer》p41.

题目:在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。

第一种思路:与题目1思路相似,利用哈希表,创建一个相同长度的辅助数组,将数组元素存在对应辅助数组下标处,很容易发现重复的数字。辅助空间O(n)

思路:二分查找。若有重复出现,则数字的个数会大于区间的长度,O(nlogn)、O(1)。

详细思路:如果数组中有重复的数,那么n+1个0~n范围内的数中,一定有几个数的个数大于1。那么,我们可以利用这个思路解决该问题。

我们把从1n的数字从中间的数字m分为两部分,前面一半为1m,后面一半为m+1n。如果1m的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字,反之,如果大于m,那么这一半的区间一定包含重复的数字;如果小于m,另一半m+1~n的区间里一定包含重复的数字。接下来,我们可以继续把包含重复的数字的区间一分为二,直到找到一个重复的数字。

由于如果1m的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字,我们可以逐步减少m,然后判断1m之间是否有重复的数,即,我们可以令m=m-1,然后再计算1m的数字的数目是否等于m,如果等于m,再令m=m-1,如果大于m,则说明1m的区间有重复的数,如果小于m,则说明m+1~n有重复的数,不断重复此过程。

/**
 * FileName: Hello
 * Author:   braincao
 * Date:     2018/8/29 15:21
 * Description: 《剑指offer》P41.不修改数组找出重复的数字
 */
public class Hello{
    public static void main(String[] args){
        int[] c = new int[]{2,3,3,5,5,2,6,7};
        System.out.println(getDuplication(c,8));
    }
    static int getDuplication(int[] array, int length){
        int left = 1;
        int right = array.length - 1;
        while(left<=right) {
            int mid = (left + right) / 2;
            int cnt = count(array, length, left, mid);
            if (left == right) {
                if (cnt > 1)
                    return left;
                else
                    break;
            }
            if (cnt > (mid - left + 1)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
	
        return -1;
    }
	
    static int count(int[] array, int length, int start, int end){
        int cnt = 0;
        for(int i=0; i<length; ++i){
            if(array[i]>=start && array[i]<=end){
                cnt++;
            }
        }
        return cnt;
    }
}

3.0~n-1中缺失的数字(2道)

0~n-1中缺失的数字(数组有序)--《剑指offer》p266
0~n-1中缺失的数字(数组无序)--leetcode 268

1.0~n-1中缺失的数字(数组有序)

《剑指offer》p266

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

思路:排序数组,问题转换成找出第一个下标与值不等的那个下标对应的数,显然是二分查找,时间o(logn)

//0~n-1中缺失的数字(数组有序)。思路:利用二分查找查找第一个nums[i]!=i的数字
public static int findLostNumber(int[] arr){
    int left = 0;
    int right = arr.length-1;
    while(left<=right){
        int mid = left+(right-left)/2;
        if(arr[mid]==mid){//nums[i]==i说明该元素与下标相符,往右边找
            left = mid+1;
        }
        else if(arr[mid]!=mid) {//nums[i]!=i说明该元素与下标不符,应该往左边找或者就是当前元素
            if(mid==left || (mid>left&&arr[mid-1]==mid-1) ){//该元素左边元素与下标相符,说明该元素是第一个不符的
                return mid;
            }
            else{//往左边找
                right = mid-1;
            }
        }
    }

    return arr.length;//之前的都相符,说明缺失的数字在最右边
}

public static void main(String[] args){
    int[] nums = new int[]{0,1,2,3,4,5,7,8,9};//0~9一共10个数,数组长度为9,缺失的数字为6
    System.out.println(findLostNumber(nums));
}

2.0~n-1中缺失的数字(数组无序)

leetcode 268

题目:Given an array containing n distinct numbers taken from 0, 1, 2, …, n, find the one that is missing from the array.

Input: [3,0,1]
Output: 2
	
Input: [9,6,4,2,3,5,7,0,1]
Output: 8

这道题的几种思路如下:

1. 二分查找(not good)
把数组排序,用二分查找来找到缺失值。时间o(nlogn)
	
1. 累加(good)
计算1+2+...+n. 用和值减去数组中数的和值,最后的差就是我们要的结果。这个过程中要防止溢出。
	
3. 异或(best)
异或运算有一个性质,x^y^y=x. 结果与x和y的顺序无关。我们把0~n与数组中的数都异或到一起,那么最后的结果就是缺失的那个数。

法一(不好):先排序,再利用二分查找找第一个下标不符的元素,时间o(nlogn),代码如下:

public int missingNumber(int[] nums) {
    Arrays.sort(nums);//先排序
    int left = 0;
    int right = nums.length-1;
    while(left<=right){
        int mid = left+(right-left)/2;
        if(nums[mid]==mid){//nums[i]==i说明该元素与下标相符,往右边找
            left = mid+1;
        }
        else if(nums[mid]!=mid) {//nums[i]!=i说明该元素与下标不符,应该往左边找或者就是当前元素
            if(mid==left || (mid>left&&nums[mid-1]==mid-1) ){//该元素左边元素与下标相符,说明该元素是第一个不符的
                return mid;
            }
            else{//往左边找
                right = mid-1;
            }
        }
    }

    return nums.length;//之前的都相符,说明缺失的数字在最右边
}

法二忽略

法三(更好):异或性质。0~n与数组中的数都异或到一起,最后的结果即为所求的缺失数字,代码如下:

public int missingNumber(int[] nums) {
    int res = 0;
    for(int i=0; i<nums.length; ++i){
        res ^= i;
        res ^= nums[i];
    }
    return res^nums.length;
}

4.数组中数字出现的次数(3道)

《剑指offer》p275、leetcode 260、leetcode 136、leetcode 137、leetcode 540

题目1:一个整型数组里只有一个数字出现了一次,其余数字都出现了两次,找出唯一一个一个出现了一次的数字。

思路1:利用异或运算的性质:异或运算相同为0,相异为1。将数组所有数字异或,最后的数字即为所求

题目2:一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。找出这两个只出现一次的数字。

思路2:

​1.所有数字异或;
​2.将最后的数字从右往左首位出现的1分为1、0两组(所求两个数字分别在两个组中);
​3.两组数字再分别进行异或,剩下的两个数即为所求(在每组中两个所求数字都只出现了一次)
​

题目3:数组中唯一只出现一次的数字。在一个数组中除一个数字只出现一次之外,其他数字都出现了三次,找出那个只出现一次的数字

思路3:

1.这里不能用异或了,但是还是考虑位运算思路;
2.将数组中所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整除,则所求只出现一次的数字二进制表示中对应的哪一位是0,否则即为1
3.位运算的好处时间o(n),空间长度32的数组o(1),比排序o(nlogn)o(1)、哈希o(n)o(n)都好

代码:

/**
 * 题目1:一个整型数组里只有一个数字出现了一次,其余数字都出现了两次,找出唯一一个一个出现了一次的数字。
 * 思路1:利用异或运算的性质:异或运算相同为0,相异为1。将数组所有数字异或,最后的数字即为所求
 *
 * 题目2:一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。找出这两个只出现一次的数字。
 * 思路2:1.所有数字异或;
 *       2.将最后的数字从右往左首位出现的1分为1、0两组(所求两个数字分别在两个组中);
 *       3.两组数字再分别进行异或,剩下的两个数即为所求(在每组中两个所求数字都只出现了一次)
 *
 * 题目3:数组中唯一只出现一次的数字。在一个数组中除一个数字只出现一次之外,其他数字都出现了三次,找出那个只出现一次的数字
 * 思路3:1.这里不能用异或了,但是还是考虑位运算思路;
 *       2.将数组中所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整除,
 *         则所求只出现一次的数字二进制表示中对应的哪一位是0,否则即为1
 *       3.位运算的好处时间o(n),空间长度32的数组o(1),比排序o(nlogn)o(1)、哈希o(n)o(n)都好
 */
public class Solution {
    //题目1:找出唯一一个一个出现了一次的数字-->所有数字异或
    public static int findSingleNumber(int[] arr){
        int res = 0;
        for(int i: arr){
            res ^= i;
        }
	
        return res;
    }
    /**
     * 题目2:找出这两个只出现一次的数字-->
     * 1.所有数字异或;
     * 2.将最后的数字从右往左首位出现的1分为1、0两组(所求两个数字分别在两个组中);
     * 3.两组数字再分别进行异或,剩下的两个数即为所求(在每组中两个所求数字都只出现了一次)
     */
    public static int[] findSingleNumber2(int[] arr){
        int tempRes = 0;
        for(int i: arr){//1.将数组所有数字异或
            tempRes ^= i;
        }
        int k=1;//2.找出tempRes从右往左首个1出现在第k位
        while(tempRes!=0){
            if((tempRes&1) == 1){ //出现了首个1
                break;
            }
            else{//还没出现首个1
                k++;
                tempRes = tempRes>>1;
            }
        }
        int groupOne = 0;//3.将原数组数字分组,并在每个组中找出只出现一个的数字
        int groupTwo = 0;
        for(int i: arr){
            int bit = (i>>(k-1)) & 1; //该数的第k位是bit
            if(bit==1){
                groupOne ^= i;
            }
            else{
                groupTwo ^= i;
            }
        }
        return new int[]{groupOne, groupTwo};
    }
	
    /**
     * 题目3:找出这两个只出现一次的数字。在一个数组中除一个数字只出现一次之外,其他数字都出现了三次,找出那个只出现一次的数字-->
     * 1.这里不能用异或了,但是还是考虑位运算思路;
     * 2.将数组中所有数字的二进制表示的每一位都加起来,如果某一位的和能被3整除,
     *   则所求只出现一次的数字二进制表示中对应的哪一位是0,否则即为1
     * 3.位运算的好处时间o(n),空间长度32的数组o(1),比排序o(nlogn)o(1)、哈希o(n)o(n)都好
     */
    public static int findSingleNumber3(int[] arr) {
        int[] bits = new int[32]; //int类型4字节,32位
        for(int number: arr){//将数组中所有数字的二进制表示的每一位都加起来,每一位的和存在bits数组中
            for(int i=0; i<32; ++i){ //从个位到第32位依次相加存储
                bits[i] += (number>>i)&1; //数字number的第i位上的数
            }
        }
        int res = 0;
        for(int i=0; i<32; ++i) { //把bits每一位上的数%3,如果为0,则所求的数该位也为0,否则为1
            res += ((bits[i]%3)<<i);
        }
        return res;
    }
	
    public static void main(String[] args){
        int[] arr = {1,2,1,2,3};//找出一个出现一次的数字
        int[] arr2 = {1,2,1,2,3,4}; //找出两次出现一次的数字
        int[] arr3 = {1,2,1,1,2,5,2}; //找出两次出现一次的数字
        System.out.println(findSingleNumber(arr));//找出一个出现一次的数字。out:3
        System.out.println(findSingleNumber2(arr2)[0] + " " + findSingleNumber2(arr2)[1]);//找出两次出现一次的数字。out:3,4
        System.out.println(findSingleNumber3(arr3));//找出这两个只出现一次的数字,其他都出现三次。out:5
    }
}

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

《剑指offer》p205、leetcode 169

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

思路:

法一:排序后位于数组中间的数即为所求。这里不排序的话,就利用partition找中位数即可—>o(n)

法二_更简单高效的方法:利用两个辅助变量num、times来计数,遍历一遍,最后的num即为所求—>o(n)

法一:排序后位于数组中间的数即为所求。这里不排序的话,就利用partition找中位数即可—>o(n)

public static int MoreThanHalfNum_Solution(int [] array) {
    int len = array.length;
    int middle = len/2;
    int left = 0;
    int right = len-1;
    int index = partition(array, left, right, left);
    while(index != middle){//如果不是中位数
        if(index < middle){//说明中位数在index的右边
            left = index + 1;
            index = partition(array, left, right, left);
        }
        else if(index > middle){//说明中位数在index的左边
            right = index - 1;
            index = partition(array, left, right, left);
        }
    }
    //现在array[index]是中位数了
    int res = 0;
    if(check(array, array[index])){//检查array[index]是否真的出现次数超过一半,不超过返回0
        res = array[index];
    }
    return res;
}
public static int partition(int[] arr, int left, int right, int pivotIndex){
    int pivot = arr[pivotIndex];
    swap(arr, pivotIndex, right);
    int storeIndex = left;
    for(int i=left; i<=right; i++){
        if(arr[i] < pivot){
            swap(arr, i, storeIndex);
            storeIndex++;
        }
    }
    swap(arr, storeIndex, right);
    return storeIndex;
}
	
public static boolean check(int[] arr, int key){//经过算法后求出的数,再次进行检查看看是否真的超过一半,不符合条件res=0
    int times = 0;
    for(int i=0; i<arr.length; ++i){
        if(arr[i] == key){
            times++;
        }
    }
    if(times > (arr.length/2)){
        return true;
    }
    return false;
}
	
public static void swap(int[] arr, int i, int j){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

法二:利用两个辅助变量num、times来计数,遍历一遍,最后的num即为所求—>o(n)

public static int MoreThanHalfNum_Solution(int [] array) {
    if(array==null || array.length==0){
        return 0;
    }
    if(array.length==1){
        return array[0];
    }
    int times = 1;
    int num = array[0];
    for(int i=1; i<array.length; ++i){
        if(array[i]==num){
            times++;
        }
        else{
            times--;
            if(times==0){
                times = 1;
                num = array[i];
            }
        }
    }

    if(times>1){//一定是
        return num;
    }
    if(times==1){//有可能不是,再重新检查该数出现次数是否超过数组长度一半,不超过返回0
        int temp = 0;
        for(int i=0; i<array.length; ++i){
            if(array[i]==num){
                temp++;
            }
        }
        if(temp > (array.length)/2){
            return num;
        }
    }
    return 0;
}
public static void main(String[] args){
    int[] array = new int[]{2,2,2,2,2,1,3,4,5};
    System.out.println(MoreThanHalfNum_Solution(array));
}

6.数组中出现次数超过1/3的数字

leetcode 229

题目:Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. Note: The algorithm should run in linear time and in O(1) space.

给你一个数组nums,如何找nums中出现次数超过总数的1/3的数,要求时间复杂度O(N)和空间复杂度O(1)

Input: [3,2,3]
Output: [3]
	
Input: [1,1,1,3,3,2,2,2]
Output: [1,2]

思路:每次从nums中拿出3个不一样的数作为一组,肯定会出现两种情况。一,nums被取空了,那么nums中每个数出现次数最多占总次数的1/3,不存在超过1/3的数字;二,还有剩余,这个情况就复杂了,有可能剩余多个,但是……但是,最多只可能剩余两种数。 为什么? 3个不同的数凑一组才能删掉,所以不可能删掉超过1/3的数。所以超过1/3的数肯定被剩下来,但是,剩下来的俩数并不一定都是超过1/3的,这点额外注意。 很容易举个例子, 比如

1 1 1 1 1 2 2 3 3 4 4 5--最后剩1,4-->只有1是结果

我们把原问题转换为如何快速高效的从数组中每次去掉3个不同的数,最后把剩下的两个不同的数保存起来,重新遍历数组判断即可。

代码实现:用a,b表示两种不同的数,用计数器cnta,cntb表示a,b不同的数出现的次数来计数,在遍历数组的过程中计数抵消,看他们俩最终还剩下多少个。

//给你一个数组nums,如何找nums中出现次数超过总数的1/3的数,要求时间复杂度O(N)和空间复杂度O(1)
//思路:遍历数组,每次抵消三个不同的数,最后剩下的2个数重新遍历数组判断。
public List<Integer> majorityElement(int[] nums) {
    List<Integer> res = new ArrayList<>();
    if(nums==null || nums.length==0){
        return res;
    }

    int a = nums[0];
    int b = nums[0];//a,b存两种不同的数
    int cnta = 0;
    int cntb = 0;//cnta,cntb对a,b两种不同的数出现次数的计数,当计数为0时,重置a/b
    for(int num: nums){//遍历字符串
        if(a==num){
            cnta++;
            continue;
        }
        if(b==num){
            cntb++;
            continue;
        }
        if(cnta==0){//a的计数为0,重置a
            a = num;
            cnta = 1;
            continue;
        }
        if(cntb==0){//b的计数为0,重置b
            b = num;
            cntb = 1;
            continue;
        }
        cnta--;
        cntb--;//当前的num和a,b都不同,则一起抵消
    }

    cnta = 0;
    cntb = 0;
    //最后剩下的有a,b,重新遍历数组查看是否次数超过1/3
    for(int num: nums){
        if(num==a){
            cnta++;
        }
        else if(num==b){
            cntb++;
        }
    }
    if(cnta>(nums.length)/3){
            res.add(a);
    }
    if(cntb>(nums.length)/3){
        res.add(b);
    }

    return res;
}

7.统计指定数字在排序数组中出现的次数

《剑指offer》p263、leetcode 34

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

思路:

因为数组排序,很明显要用二分查找。

法一(不可取),最差时间o(n)。先二分查找k在数组array中的索引,然后从该索引处向左向右外扩,找出所有相等的数。

法二(更好的方法):充分利用二分查找直接找到第一个k和最后一个k,二者索引相减即为出现的个数。时间o(logn)

什么时候是第一个k:找到的k的前面一个元素如果不等于k,则此时是第一个k

什么时候是最后一个k:找到的k的后面一个元素如果不等于k,则此时是第一个k

//统计指定数字在排序数组中出现的次数。思路:充分利用二分查找第一个k和最后一个k的位置,两者的区间长度即为所求次数
public static int GetNumberOfK(int [] array , int k) {
    if(array==null || array.length==0){
        return 0;
    }
    //二分查找左边第一个k的位置
    int left = getFirst(array, k);
    int right = getLast(array, k);
    if(left==-1 || right==-1){//数组中没有k
        return 0;
    }
    if(left<=right){
        return right-left+1;
    }
    return 0;
}
//二分查找第一个k的位置
public static int getFirst(int[] nums, int k){
    int left = 0;
    int right = nums.length-1;
    while(left<=right){
        int mid = left + (right-left)/2;
        if(nums[mid]==k){
            if( (mid>left&&nums[mid-1]!=k) || mid==left){//找到第一个k
                return mid;
            }
            else{//不是第一个k,继续往左边找
                right = mid-1;
            }
        }
        else if(nums[mid]<k){//k在右边
            left = mid+1;
        }
        else{//k在左边
            right = mid-1;
        }
    }
    return -1;//没找到k,返回-1
}
//二分查找最后一个k的位置
public static int getLast(int[] nums, int k){
    int left = 0;
    int right = nums.length-1;
    while(left<=right){
        int mid = left + (right-left)/2;
        if(nums[mid]==k){
            if( (mid<right&&nums[mid+1]!=k) || mid==right){//找到最后一个k
                return mid;
            }
            else{//不是最后一个k,继续往右边找
                left = mid+1;
            }
        }
        else if(nums[mid]<k){//k在右边
            left = mid+1;
        }
        else{//k在左边
            right = mid-1;
        }
    }
    return -1;//没找到k,返回-1
}

public static void main(String[] args){
    int[] nums = new int[]{1,2,3,4,5,5,5,5,5,5,5,7,8,11};//out:7
    System.out.println(GetNumberOfK(nums, 5));
}

8.二维数组中的查找

《剑指offer》p44、leetcode74

题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

思路:从二维数组右上角开始查找,因为从右上角/左下角开始比较的话若不相等可以删除不符合条件的一行或者一列

以右上角为例,若相等则返回true;若target比右上角大,说明目标在其下面,则删除所在行;若target比右上角小,说明目标在其左侧,则删除所在列

以左上角为例,若target比左上角大,则不能缩小范围,因为右侧和下侧的元素都比左上角,右下角同理,pass!

public boolean Find(int target, int [][] array) {
    if(array==null || array.length==0){
        return false;
    }
    int m = array.length-1;
    if(array[0].length==0){
        return false;
    }
    int n = array[0].length-1;
    int i = 0;
    int j = n;
    while(i<=m && j>=0){
        if(array[i][j] == target){
            return true;
        }
        else if(target<array[i][j]){
            j--;
        }
        else{
            i++;
        }
    }
    return false;
}

9.替换空格

《剑指offer》p51

题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:先遍历字符串计算空格出现次数,然后设置新数组大小,从后往前遍历数组,替换空格。

public class Solution {
    public String replaceSpace(StringBuffer str) {
        int cnt = 0;
        for(int i=0; i<str.length(); i++)
        {
            if(str.charAt(i)==' ')
            {
                cnt++;
            }
        }
        int newLen = str.length() + cnt*2;
        char[] newStr = new char[newLen];
        int i = str.length()-1;
        int j = newLen-1;
        while(i>=0){
            if(str.charAt(i)==' '){
                newStr[j--] = '0';
                newStr[j--] = '2';
                newStr[j--] = '%';
            }
            else{
                newStr[j--] = str.charAt(i);
            }
            i--;
        }
	
        return new String(newStr);
    }
}

10.Replace Words–字典树

LeetCode 648. Replace Words 字典树练习

题目:In English, we have a concept called root, which can be followed by some other words to form another longer word - let’s call this word successor. For example, the root an, followed by other, which can form another word another.

Now, given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with theroot forming it. If a successor has many roots can form it, replace it with the root with the shortest length.

You need to output the sentence after the replacement.

Example 1:

Input: dict = ["cat", "bat", "rat"]
sentence = "the cattle was rattled by the battery"
Output: "the cat was rat by the bat"

题意:用字典中存在的前缀代替句子中的单词,若有多个前缀可以表示单词,则选择最短的一个

暴力思路:1.将sentence分成words数组,每个单词进行前缀判断,有词根的换成词根(换长度最小的那个); 2.将words数组重新组成sentence返回即可

class Solution {
    public String replaceWords(List<String> dict, String sentence) {
        //1.将sentence分成words数组,每个单词进行前缀判断,有词根的换成词根
        String[] words = sentence.split(" ");
        for(int i=0; i<words.length; ++i){
            int min = Integer.MAX_VALUE;
            for(String root: dict){
                if(words[i].startsWith(root) && root.length()<min){
                    words[i] = root;
                }
            }
        }
	
        //2.将words数组重新组成sentence返回即可
        StringBuilder res = new StringBuilder();
        for(int i=0; i<words.length; ++i){
            res.append(words[i] + " ");
        }
        return res.toString().trim();
    }
}

优化思路(字典树):这道题给了我们一个前缀字典,又给了一个句子,让我们将句子中较长的单词换成其前缀(如果在前缀字典中存在的话)。我们对于句子中的一个长单词如何找前缀呢,是不是可以根据第一个字母来快速定位呢,比如cattle这个单词的首字母是c,那么我们在前缀字典中找所有开头是c的前缀,为了方便查找,我们将首字母相同的前缀都放到同一个数组中,总共需要26个数组,所以我们可以定义一个二维数组来装这些前缀。还有,我们希望短前缀在长前缀的前面,因为题目中要求用最短的前缀来替换单词,所以我们可以先按单词的长度来给所有的前缀排序,然后再依次加入对应的数组中,这样就可以保证短的前缀在前面。

提交显示暴力思路50ms,优化思路19ms,优化的还不错。

public static String replaceWords(List<String> dict, String sentence) {
    //1.roots数组按长度排序
    Collections.sort(dict);
	
    //2.构建字典树,采用HashMap,用首字母索引,首字母相同的前缀都放到同一个数组中,总共需要26个数组
    Map<Integer, ArrayList<String>> wordTree = new HashMap<>();
    for(int i=0; i<dict.size(); ++i){
        int wordIndex =  dict.get(i).charAt(0)-'a';
        if(!wordTree.containsKey(wordIndex)){
            wordTree.put(wordIndex, new ArrayList<String>());
        }
        wordTree.get(wordIndex).add(dict.get(i));
    }
	
    //3.将sentencesplit(" ")分成words单词数组,每个单词通过字典树进行前缀判断,有词根的换成词根
    String[] words = sentence.split(" ");
    for(int i=0; i<words.length; ++i){
        int wordIndex = words[i].charAt(0)-'a';
        if(wordTree.containsKey(wordIndex)){
            for(int j=0; j<wordTree.get(wordIndex).size(); ++j){
                if(words[i].startsWith(wordTree.get(wordIndex).get(j))){
                    words[i] = wordTree.get(wordIndex).get(j);
                }
            }
        }
    }
	
    //4.将words数组重新组成sentence返回即可
    StringBuilder res = new StringBuilder();
    for(int i=0; i<words.length; ++i){
        res.append(words[i] + " ");
    }
    return res.toString().trim();
}

11.Find And Replace in String

LeetCode 833. Find And Replace in String

题目:To some string S, we will perform some replacement operations that replace groups of letters with new ones (not necessarily the same size).

Each replacement operation has 3 parameters: a starting index i, a source word x and a target word y. The rule is that if x starts at position i in the original string S, then we will replace that occurrence of x with y. If not, we do nothing.

For example, if we have S = “abcd” and we have some replacement operation i = 2, x = “cd”, y = “ffff”, then because “cd” starts at position 2 in the original string S, we will replace it with “ffff”.

Using another example on S = “abcd”, if we have both the replacement operation i = 0, x = “ab”, y = “eee”, as well as another replacement operation i = 2, x = “ec”, y = “ffff”, this second operation does nothing because in the original string S[2] = ‘c’, which doesn’t match x[0] = ‘e’.

All these operations occur simultaneously. It’s guaranteed that there won’t be any overlap in replacement: for example, S = “abc”, indexes = [0, 1], sources = [“ab”,“bc”] is not a valid test case.

Example 1:

Input: S = "abcd", indexes = [0,2], sources = ["a","cd"], targets = ["eee","ffff"]
Output: "eeebffff"
Explanation: "a" starts at index 0 in S, so it's replaced by "eee".
"cd" starts at index 2 in S, so it's replaced by "ffff".

Example 2:

Input: S = "abcd", indexes = [0,2], sources = ["ab","ec"], targets = ["eee","ffff"]
Output: "eeecd"
Explanation: "ab" starts at index 0 in S, so it's replaced by "eee". 
"ec" doesn't starts at index 2 in the original S, so we do nothing.

题目大意:给了原始的字符串S,给出了要开始替换的位置indexes,判断S在indexes的位置向后是否能匹配sources中对应位置的元素,如果相等,则把S的该部分替换成targets对应的部分。

思路:

不可能直接对S进行替换操作的,因为那样直接改变了S的值和长度,影响以后的匹配操作。

而应该将原字符串S按照indexs拆分成几段子字符串,然后分别进行替换,最终拼接返回即可。同时应该从右往左处理原字符串,替换用replaceFirst()

将indexes按逆序排序,然后对S从右往左依次查找可替换的单词,如果出现在指定位置,则替换。由于indexes排序后,会变化,因此需要用map结构保存原来的索引。

public static String findReplaceString(String S, int[] indexes, String[] sources, String[] targets) {
    if(S==null || S.length()==0){
        return S;
    }
    //由于indexes排序后,会变化,因此需要用map结构保存原来的索引。
    Map<Integer, Integer> map = new HashMap<>();//key:index[i] value:i
    for(int i=0; i<indexes.length; ++i){
        map.put(indexes[i], i);
    }
    //将indexes按排序
    Arrays.sort(indexes);
    //将原字符串S按照indexs拆分成几段子字符串,然后分别进行判断
    String[] words = new String[indexes.length+1];
    //字符串从右往左处理,同时index也从最大到最小
    for(int i=indexes.length-1; i>=0; --i){
        String last;//当前需要处理的子字符串,判断是否需要替换
        if(i==indexes.length-1){
            last = S.substring(indexes[i]);
        }
        else{
            last = S.substring(indexes[i], indexes[i+1]);
        }
        int tempIndex = map.get(indexes[i]); //当前字符串对应的index
        if(last.startsWith(sources[tempIndex])){
            words[i+1] = last.replaceFirst(sources[tempIndex],targets[tempIndex]);
        }
        else{
            words[i+1] = last;
        }
    }
    words[0] = S.substring(0,indexes[0]).replaceFirst(sources[0],targets[0]);//最前面的字符串
    
    //将替换完的words数组拼接成最终的字符串
    StringBuilder res = new StringBuilder();
    for(int i=0; i<words.length; ++i){
        res.append(words[i]);
    }
    return res.toString();
}

12.从尾到头打印链表

《剑指offer》p58.

题目:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

思路:

法一:从头遍历链表放进ArrayList中,最后Collections.reverse即可

法二:反转链表(头插法),之后从头遍历放进ArrayList中即可

法三:用栈辅助,从头遍历放进栈中,之后从栈弹出ArrayList中即可

法四:递归实现,从头遍历链表,但是递归实现

//法一:从头遍历链表放进ArrayList中,最后Collections.reverse即可
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    ListNode p = listNode;
    ArrayList<Integer> res = new ArrayList<>();
    while(p!=null){
        res.add(p.val);
        p = p.next;
    }
    Collections.reverse(res);
    return res;
}
    
//法二:反转链表(头插法),之后从头遍历放进ArrayList中即可
public ArrayList<Integer> printListFromTailToHead2(ListNode listNode) {
    ArrayList<Integer> res = new ArrayList<>();
    if(listNode==null){
        return res;
    }
    ListNode p = listNode;
    ListNode cur = p.next;
    p.next = null;
    while(cur!=null){
        ListNode temp = cur;
        cur = cur.next;
        temp.next = p;
        p = temp;
    }
    while(p!=null){
        res.add(p.val);
        p = p.next;
    }
    return res;
}
    
//法三:用栈辅助,从头遍历放进栈中,之后从栈弹出ArrayList中即可
public ArrayList<Integer> printListFromTailToHead3(ListNode listNode) {
    ArrayList<Integer> res = new ArrayList<>();
    if(listNode==null){
        return res;
    }
    Stack<Integer> stack = new Stack<>();
    ListNode p = listNode;
    while(p!=null){
        stack.push(p.val);
        p = p.next;
    }
    while(!stack.isEmpty()){
        res.add(stack.pop());
    }
    return res;
}
    
//法四:递归实现,从头遍历链表,但是递归实现
ArrayList<Integer> res = new ArrayList<>();
public ArrayList<Integer> printListFromTailToHead4(ListNode listNode) {
    if(listNode!=null){
        printListFromTailToHead4(listNode.next);
        res.add(listNode.val);
    }
    return res;
}

13.反转链表(三道)

1.反转全部链表

《剑指offer》p142、《左神》p40、leetcode206

题目:输入一个链表,反转链表后,输出新链表的表头。

思路:头插法

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null || head.next==null){
            return head;
        }
        ListNode newHead = head;
        head = head.next;
        newHead.next = null; //这步别忘了
        while(head!=null){
            ListNode tempNode = head;
            //原链表继续遍历下一个
            head = head.next;
            //头插法
            tempNode.next = newHead;
            newHead = tempNode;
        }
        return newHead;
    }
}

2.反转部分链表

《左神》p42、leetcode92

题目:这道题目规定了要进行反转的位置区间。

Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL

思路:我们要先添加一个哨兵,指向head节点(最后返回头指针的next即可);然后一直往后访问,一直到要反转的节点的前一位停下来。

我们要记录下两个节点的位置:开始反转位置的节点的前一位、开始反转位置的节点。因为在反转后,开始反转的节点的前一个节点的next指针要指向反转的最后一个节点,开始反转的节点的next要指向反转的最后一个节点的后一个节点。

然后进行与上面一题同样的反转即可

public ListNode reverseBetween(ListNode head, int m, int n) {
    if(head==null || head.next==null || m>=n || m<=0){
        return head;
    }
    
    //1.哨兵,这样不用考虑m==1从头反转的问题,都转化成从中间反转
    ListNode guard = new ListNode(0);
    guard.next = head;
    ListNode p = guard;
    //2..将p移到要反转部分的前一个节点,移动次数为m-1次
    for(int i=1; i<=m-1; ++i){
        p = p.next;
        if(p==null){//如果遍历完链表都没有加到m,则不需要反转
            return head;
        }
    }
    
    if(p.next==null || p.next.next==null){//需要反转的部分为空或者只有一个节点,则不需要反转
        return head;
    }
    
    //3.保存反转部分的前一个节点
    ListNode pre = p;
    
    //4.反转部分链表(头插法)
    p = p.next;
    ListNode cur = p.next;
    p.next=null;
    ListNode last = p;//保存反转部分的最后一个节点
    for(int i=m; i<n; ++i){
        ListNode temp = cur;
        cur = cur.next;
        temp.next = p;
        p = temp;
        if(cur==null){
            break;
        }
    }
    
    //5.拼接反转部分前,反转部分,反转部分后的链表
    last.next = cur;
    pre.next = p;
    
    return guard.next;
}

3.分组翻转链表

leetcode 25 Reverse Nodes in k-Group、《左神》68

题目:将链表的每k个节点逆序。给定一个单链表的头结点head,实现一个调整单链表的函数,使得每k个节点之间逆序,如果最后不管k个节点一组,则不调整最后几个节点。

Example:

Given this linked list: 1->2->3->4->5
For k = 2, you should return: 2->1->4->3->5
For k = 3, you should return: 3->2->1->4->5

思路:先遍历一次链表记录长度,然后分组进行翻转即可。

public ListNode reverseKGroup(ListNode head, int k) {
    if(head==null || head.next==null || k<2){
        return head;
    }
    //1.遍历一遍链表记录链表长度
    int len = 0;
    ListNode p = head;
    while(p!=null){
        len++;
        p = p.next;
    }
    
    //2.分组翻转链表
    ListNode guard = new ListNode(0);//哨兵
    guard.next = head;
    ListNode pre = guard;//缓存翻转后的尾节点指针
    ListNode cur = guard.next;//哨兵
    ListNode last = cur;//缓存翻转后的尾节点指针
    while(len>=k){//翻转这么多次
        len -= k;
        int tempK = k;
        
        while(tempK!=0){//每次翻转k个节点
            ListNode temp = cur;
            cur = cur.next;
            temp.next = pre.next;//头插法
            pre.next = temp;
            tempK--;
        }
        last.next = cur;//翻转后的链表尾部连接到需要反转部分的下一个节点
        pre = last;
        last = cur;
    }
    return guard.next;
}

14.根据前序+中序遍历数组来重建二叉树

《剑指offer》p62、leetcode105

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:在函数recon中,先根据前序遍历数组的第一个数字创建根节点,之后在中序遍历数组中找到根节点的位置,这样就能确定左、右子树节点的数量。在前序遍历和中序遍历数组中划分了左、右子树节点的值之后,就可以递归地调用函数recon去分别构建它的左、右子树。

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
public class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder==null || preorder.length==0){
            return null;
        }
        return recon(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1);
    }
    //递归重建二叉树
    public TreeNode recon(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd){
        if(preStart>preEnd || inStart>inEnd){
            return null;
        }
        TreeNode root = new TreeNode(preorder[preStart]);//前序遍历第一个节点为根节点
        int rootIndex = findRootIndex(inorder, inStart, inEnd, root.val);//前序遍历的根节点在中序遍历数组的位置索引
        int leftCnt = rootIndex-inStart;//左子树节点的个数
        root.left = recon(preorder, preStart+1, preStart+leftCnt, inorder, inStart, rootIndex-1);
        root.right = recon(preorder, preStart+leftCnt+1, preEnd, inorder, rootIndex+1, inEnd);
	
        return root;
    }
	
    //找到根节点在中序遍历中的位置
    public int findRootIndex(int[] order, int start, int end, int root){
        int index = -1;
        for(int i=start; i<=end; ++i){
            if(order[i]==root){
                index = i;
            }
        }
        return index;
    }
}

15.二叉树的下一个节点

《剑指offer》p65.

题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路:这个节点可以分为三种情况

1.该节点有右子树:下一个节点就是它的右子树中最左子节点

2.该节点没有右子树且是父节点的左子节点:下一个节点就是它的父节点

3.该节点没有右子树且是父节点的右子节点:沿着指向父节点的指针一直向上遍历,直到找到一个是它父节点的左子节点的节点,如果这样的节点存在,那么这个节点的父节点就是要找的下一个节点

class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;
	
    TreeLinkNode(int val) {
        this.val = val;
    }
}
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode == null){
            return null;
        }
        //如果该节点有右子树,返回其右子树中最左的子节点
        if(pNode.right != null){
            pNode = pNode.right;
            while(pNode.left != null){
                pNode = pNode.left;
            }
            return pNode;
        }
        //如果该节点没有右子树
        while(pNode.next != null){
            //该节点是父节点的左子节点,返回父节点
            if(pNode.next.left== pNode){
                return pNode.next;
            }
            //该节点是父节点的右子节点,沿着指向父节点的指针一直向上遍历,
            // 直到找到一个是它父节点的左子节点的节点,如果这样的节点存在,那么这个节点的父节点就是要找的下一个节点
            pNode = pNode.next;
        }
        //如果第3中情况下一直向上遍历到根节点,则没有要找的下一个节点,返回null
        return null;
    }
}

16.Populating Next Right Pointers in Each Node

leetcode 116.

题目:Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL.

Initially, all next pointers are set to NULL.

题目大意:将满二叉树每个节点的next指针赋值,每个节点的next指针指向同一层的下一个节点,要求空间复杂度是O(1)。本题的前提是给定的二叉树是满二叉树,即所有的叶子节点都在同一层。举例如下

原始:

     1
   /  \
  2    3
 / \  / \
4  5  6  7

变为:

     1 -> NULL
   /  \
  2 -> 3 -> NULL
 / \  / \
4->5->6->7 -> NULL

思路:如果有下一层的话,为下一层的子节点的next指针赋值。每层都设置一个哨兵,代表下一层的第一个节点。本题的哨兵思想是解题利器,并且不用递归,循环层即可完成

public void connect(TreeLinkNode root)
{
    TreeLinkNode pRoot = root;
    TreeLinkNode guard = new TreeLinkNode(0);//哨兵,指向下一层的第一个节点
    while(pRoot!=null){//层的循环
        guard.next=null;//哨兵,指向下一层的第一个节点
        TreeLinkNode cur = guard;
        if(pRoot.left!=null) {//如果有下层的话
            while(pRoot!=null){//依次为下一层的每个节点next指针
                cur.next = pRoot.left;
                cur.next.next = pRoot.right;
                cur = cur.next.next;
                pRoot = pRoot.next;
            }
        }
        pRoot = guard.next;//进入下一层的第一个节点
    }
}

17.Populating Next Right Pointers in Each Node II

leetcode 117.Populating Next Right Pointers in Each Node

题目:本题要求和上面一样,不同的是这里给定的二叉树不一定是满二叉树。You may only use constant extra space

原始:

     1
   /  \
  2    3
 / \    \
4   5    7

变为:

     1 -> NULL
   /  \
  2 -> 3 -> NULL
 / \    \
4-> 5 -> 7 -> NULL

思路:因为可能不是满二叉树而是任意二叉树,所以下一层的第一个节点需要手动寻找,我们要寻找下层的第一个节点first,为了避免这个寻找,每层都设置一个哨兵,代表下一层的第一个节点。本题的哨兵思想是解题利器

public void connect(TreeLinkNode root)
{
    TreeLinkNode pRoot = root;
    //我们要寻找下层的第一个节点first,为了避免这个寻找,每层都设置一个哨兵,代表第一个节点
    TreeLinkNode guard = new TreeLinkNode(0);//哨兵,指向下一层的第一个节点
    while(pRoot!=null){
        guard.next=null;//哨兵,指向下一层的第一个节点
        TreeLinkNode cur = guard;
    
        while(pRoot!=null){//依次为下一层的节点next指针赋值
            if(pRoot.left!=null){
                cur.next = pRoot.left;
                cur = cur.next;
            }
            if(pRoot.right!=null){
                cur.next = pRoot.right;
                cur = cur.next;
            }
            pRoot = pRoot.next;
        }
    
        pRoot = guard.next;//进入下一层(下一层的root从下一层的第一个节点guard.next开始)
    }
}

18.两个栈实现队列

《剑指offer》p68、leetcode232、《左神》5

题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

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);
    }
	
    //出队
    public int pop() {
        if(stack2.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

19.两个队列实现栈

《剑指offer》p71、leetcode225

题目:两个队列实现栈

class MyStack {
    Queue<Integer> que1;
    Queue<Integer> que2;
    /** Initialize your data structure here. */
    public MyStack() {
        que1 = new LinkedList<>();
        que2 = new LinkedList<>();
    }
	
    /** Push element x onto stack. */
    public void push(int x) {
        if(!que1.isEmpty()){
            que1.offer(x);
        }
        else{
            que2.offer(x);
        }
    }
	
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        while(!que1.isEmpty()){
            int temp = que1.poll();
            if(que1.isEmpty()){
                return temp;
            }
            else{
                que2.offer(temp);
            }
        }
	
        while(!que2.isEmpty()){
            int temp = que2.poll();
            if(que2.isEmpty()){
                return temp;
            }
            else{
                que1.offer(temp);
            }
        }
	
        return -1;
    }
	
    /** Get the top element. */
    public int top() {
        while(!que1.isEmpty()){
            int temp = que1.poll();
            que2.offer(temp);
            if(que1.isEmpty()){
                return temp;
            }
        }
	
        while(!que2.isEmpty()){
            int temp = que2.poll();
            que1.offer(temp);
            if(que2.isEmpty()){
                return temp;
            }
        }
        return -1;
    }
	
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return que1.isEmpty() && que2.isEmpty();
    }
}

20.斐波那契数列(三道)

斐波那契数列--《剑指offer》p75 
青蛙跳台阶--《剑指offer》p77、leetcode 70
变态跳台阶--牛客网 

1.斐波那契数列

《剑指offer》p75

题目:大家都知道斐波那契数列f(n)=f(n-1)+f(n-2),现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39

思路:动态规划问题。解法:自上而下递归分析,自下而上循环实现。

public static int Fibonacci(int n) {
    if(n==0){
        return 0;
    }
    if(n==1){
        return 1;
    }
    int n1 = 0;
    int n2 = 1;
    for(int i=2; i<=n; ++i){
        int temp = n1+n2;
        n1 = n2;
        n2 = temp;
    }
    return n2;
}

2.青蛙跳台阶

《剑指offer》p77、leetcode 70

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)

思路:就是斐波那切数列

public int JumpFloor(int target) {
    if(target==1){
        return 1;
    }
    if(target==2){
        return 2;
    }
    int n1 = 1;
    int n2 = 2;
    for(int i=3; i<=target; ++i){
        int temp = n1 + n2;
        n1 = n2;
        n2 = temp;
    }
    return n2;
}

3.变态跳台阶

牛客网上的

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

思路:动态规划分析:f(n)=f(n-1)+f(n-2)+…f(1)+1,因此需要数组来缓存之前的值,但其实不需要数组,直接用变量来缓存即可,即f(n)=之前的和+1。

public int JumpFloorII(int target) {
    if(target==1){
        return 1;
    }
    if(target==2){
        return 2;
    }
    int sumTemp = 3;//缓存之前的和
    int resTemp = 0;
    for(int i=3; i<=target; ++i){
        resTemp = sumTemp+1;//当前的结果值
        sumTemp += resTemp; //把当前的结果值加到缓存和中
    }
    return resTemp;
}

21.斐波那契数列变种(两道)

Length of Longest Fibonacci Subsequence--leetcode 873 
Split Array into Fibonacci Sequence--leetcode842 

1.Length of Longest Fibonacci Subsequence

leetcode 873

题目:给定一个严格递增的正整数数组形成序列 A ,找到A中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。

子序列是从原序列 A 中派生出来的,它从 A 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列

Example 1:

Input: [1,2,3,4,5,6,7,8]
Output: 5
Explanation:
The longest subsequence that is fibonacci-like: [1,2,3,5,8].

思路:动态规划问题。二维dp问题。

使用一维DP解决不了这个问题,因为一维DP只保存了到某个为止的最长斐波那契数列,但是新的数字到来之后能不能满足之前的费布拉奇数列是未知的。所以使用二维DP。

dp[i][j]表示以A[i]、A[j]结尾的最长斐波那契数列长度,初始化为2。

核心方程:dp[j][k]=dp[i][j]+1

条件是 A[i] + A[j] = A[k]

这个算法的时间复杂度是O(n2),空间复杂度是O(n2).

//二维dp问题。dp[i][j]表示以A[i]、A[j]结尾的最长斐波那契数列长度,初始化为2。
//核心方程:dp[j][k]=dp[i][j]+1,A[k]-A[j]=A[i]
public static int lenLongestFibSubseq(int[] A) {
    if(A==null || A.length==0){
        return 0;
    }
    int m = A.length;
    int[][] dp = new int[m][m];//dp[i][j]表示以A[i]、A[j]结尾的斐波那契数列长度,初始化为2
    for(int i=0; i<m; i++){
        for(int j=i+1; j<m; ++j){
            dp[i][j] = 2;
        }
    }
    
    Map<Integer, Integer> map = new HashMap<>();//map存放A[]数组便于下面查找。key:a[i], value:i
    for(int i=0; i<A.length; ++i){
        map.put(A[i], i);
    }
    
    //dp[j][k]=dp[i][j]+1,A[k]-A[j]=A[i]
    int res = 0;
    for(int j=0; j<m; j++){
        for(int k=j+1; k<m; ++k){
            int a_i = A[k]-A[j];
            if(a_i<A[j] && map.containsKey(a_i)){
                dp[j][k] = Math.max(dp[j][k], dp[map.get(a_i)][j]+1);
                res = Math.max(res, dp[j][k]);
            }
        }
    }
    
    return res;
}

2.Split Array into Fibonacci Sequence

leetcode 842、与leetcode 306类似

题目:给出了一个有0-9数字组成的纯数字字符串。判断能否组成斐波那契数列。注意这个题注重点在不管你几位数字去划分,只要满足后面的数字等于前两个的和即可。最终要返回的是任何一个组合即可。

Example 1:

Input: "123456579"
Output: [123,456,579]

思路:回溯法。本题和下面的刚一看这个题的时候没有思路,知道应该是用dfs或backtracking一类的算法。

解题的关键在于确定前两个数,只要确定了前两个数,后面的数可以依次计算出来,要么符号条件要么不符合条件。那么如何确定前两个数?

首先分析如何确定第一个数,第一个数的最小的长度是1(只包含一个数字),最大的长度是(L-1)/2,其中L为字符串的长度,最大的长度一定小于总长度的一半,比如如果总长度是5,第一个数长度不能超过2,如果总长度是6,第一个数长度也不能超过2,所以最大的长度是(L-1)/2,。

再确定第二个数的范围,第二个数从第一个数后面开始,第三个数从第二个数后面开始,我们首先知道,第三个数肯定至少和第一个数与第二个数一样大,因为是和嘛,若第二个数从i开始到j-1结束,那么第三个数长度最大为L-j,这个长度一定大于等于第一个数和第二个数长度的较大者,即:L-j>=i && L-j>=j-i。

确定了第二个数的范围之后问题就简单了,看看能不能构成加法序列,只要判断剩下的字串是不是以sum开头即可,然后递归判定。

这题的关键在于找前两个数,前两个数确定了,后面的依次判断即可。第一个数肯定从0开始,但最长是(L-1)/2,第二个数从第一个数后面开始,第三个数至少跟第一个和第二个较大的数一样长,即第三个数长度不小于第一个数 && 第三个数长度不小于第二个数

本题需要把斐波那契数列的各个数字存起来,同时还要注意要防止int溢出

public List<Integer> splitIntoFibonacci(String S) {
    int L = S.length();
    List<Integer> res= new ArrayList<>();
    
    for(int i=1; i<=(L-1)/2; ++i){//第一个数的长度为[1,(L-1)/2]
        String s1 = S.substring(0,i);//第一个数
        if(S.startsWith("0") && s1.length()>1){//第一个数长度超过2起始位不能为0
            break;
        }
        Long num1 = Long.valueOf(s1);
        if(num1>Integer.MAX_VALUE){//防止int溢出
            break;
        }
        for(int j=i+1;j<=L-1&&(L-j)>=(j-i)&&(L-j)>=i; ++j){//第三个数大于等于第一、第二个数
            String s2 = S.substring(i,j);//第二个数
            if(S.charAt(i)=='0' && s2.length()>1){//第二个数长度超过2起始位不能为0
                break;
            }
            Long num2 = Long.valueOf(s2);
            if(num2>Integer.MAX_VALUE){//防止int溢出
                break;
            }
    
            if(isValid(S.substring(j), num1, num2, res)){//进行斐波那契判断
                return res;
            }
        }
    }
    
    res.clear();
    return res;
}
    
public boolean isValid(String S, long num1, long num2, List<Integer> temp){
    if(S.equals("")){//递归结束
        temp.add((int) num1);
        temp.add((int) num2);
        return true;
    }
    long sum = num1 + num2;
    if(sum>Integer.MAX_VALUE){//防止int溢出
        return false;
    }
    String sumStr = "" + sum;
    if(sumStr.length()>1 && S.startsWith("0")){//如果和的长度大于1,则起始位不能是0
        return false;//剪枝,回溯点
    }
    if(S.startsWith(sumStr)){
        temp.add((int) num1);
        return isValid(S.substring(sumStr.length()), num2, sum, temp);//递归判断
    }else{
        temp.clear();
        return false;
    }
}

22.Additive Number

leetcode 306

题目:给出了一个有0-9数字组成的纯数字字符串。判断它能不能组成所谓的“加法数字”,即斐波那契数列。注意这个题注重点在不管你几位数字去划分,只要满足后面的数字等于前两个的和即可。

Note: Numbers in the additive sequence cannot have leading zeros, so sequence 1, 2, 03 or 1, 02, 3 is invalid.

Input: "112358"
Output: true 
Explanation: The digits can form an additive sequence: 1, 1, 2, 3, 5, 8. 
1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
	
Input: "199100199"
Output: true 
Explanation: The additive sequence is: 1, 99, 100, 199.
1 + 99 = 100, 99 + 100 = 199

思路:回溯法。刚一看这个题的时候没有思路,知道应该是用dfs或backtracking一类的算法。

解题的关键在于确定前两个数,只要确定了前两个数,后面的数可以依次计算出来,要么符号条件要么不符合条件。那么如何确定前两个数?

首先分析如何确定第一个数,第一个数的最小的长度是1(只包含一个数字),最大的长度是(L-1)/2,其中L为字符串的长度,最大的长度一定小于总长度的一半,比如如果总长度是5,第一个数长度不能超过2,如果总长度是6,第一个数长度也不能超过2,所以最大的长度是(L-1)/2,。

再确定第二个数的范围,第二个数从第一个数后面开始,第三个数从第二个数后面开始,我们首先知道,第三个数肯定至少和第一个数与第二个数一样大,因为是和嘛,若第二个数从i开始到j-1结束,那么第三个数长度最大为L-j,这个长度一定大于等于第一个数和第二个数长度的较大者,即:L-j>=i && L-j>=j-i。

确定了第二个数的范围之后问题就简单了,看看能不能构成加法序列,只要判断剩下的字串是不是以sum开头即可,然后递归判定。

这题的关键在于找前两个数,前两个数确定了,后面的依次判断即可。第一个数肯定从0开始,但最长是(L-1)/2,第二个数从第一个数后面开始,第三个数至少跟第一个和第二个较大的数一样长,即第三个数长度不小于第一个数 && 第三个数长度不小于第二个数

//回溯法问题。关键在于确定前两个数即可。
//三个数的长度: 第一个数,[0,i),长度为i;第二个数,[i,j)长度为j-i; 第三个数(和):[j,L-1],长度L-j
//三个数的长度最长的范围:第一个数长度上限:x=(L-1)/2; 第二个数长度下限:y=x,第三个数长度下限: y
public static boolean isAdditiveNumber(String num) {
    int L = num.length();
    for(int i=1; i<=(L-1)/2; ++i){//i从1起,第一个数长度至少为1
	
        if(num.startsWith("0") && i>1){
            break;//第一个数长度大于1,不能以0开头,这里break而不是continue,好,直接省去很多不必要的循环
        }
	
        for(int j=i+1; j<=L-1 && (L-j)>=i && (L-j)>=(j-i); ++j){//第三个数长度不小于第一个数,第三个数长度不小于第二个数
            if(num.charAt(i)=='0' && j-i>1){
                break;//第二个数长度大于1,不能以0开头,这里break而不是continue,好,直接省去很多不必要的循环
            }
	
            long num1 = Long.valueOf(num.substring(0, i));//第一个数
            long num2 = Long.valueOf(num.substring(i, j));//第二个数
	
            if(isAdditive(num.substring(j), num1, num2)){
                return true;//找到满足斐波那契条件的一个划分序列
            }
        }
    }
	
    return false;
}
	
public static boolean isAdditive(String num, long num1, long num2){
    if(num.equals("")){
        return true; //递归结束,回溯点
    }
    long sum = num1+num2;
    String sumStr = "" + sum;
    int sumLen = sumStr.length();
    if(sumLen>1 && num.startsWith("0")){//如果和的长度大于1,则起始位不能是0
        return false;//剪枝,回溯点
    }
    if(num.startsWith(sumStr)){//如果和在字符串中,则进行下一次递归
        return isAdditive(num.substring(sumLen), num2, sum);
    }
    return false;
}
	
public static void main(String[] args){
    String num = "123";
    System.out.println(isAdditiveNumber(num));
}

23.旋转数组的最小数字(三道)

《剑指offer》p82
Find Minimum in Rotated Sorted Array--leetcode153
Find Minimum in Rotated Sorted Array 2--leetcode154

1.旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

普通二分查找思路:顺序解法o(n),从头到尾遍历数组一次,我们就能找出最小的数字。但本题部分排序,排序的题优先考虑二分查找,因此采用二分查找的双指针的思路,o(logn)。

两个指针代表两大阵营(原数组/旋转的那部分数组),最终会相邻,第二个指针指向的刚好是最小的元素

//这里是部分排序的数组,考虑二分查找
public static int minNumberInRotateArray(int [] array) {
    if(array==null){
        return 0;
    }
    int len = array.length;
    if(len==0){
        return 0;
    }
    if(len==1){
        return array[0];
    }
    int left = 0;
    int right = len-1;
    while(left<right){//二分查找
        //结束条件:两个指针最终会相邻,代表两大阵营(原数组/旋转的那部分数组),第二个指针指向的刚好是最小的元素
        if(left+1==right){//结束条件
            if(nums[right]<nums[0]){//也可能存在原数组没旋转的情况
                return nums[right];
            }else{
                return nums[0];
            }
        }
        int mid = (left+right)/2;
        //特殊情况,当left、right、mid对应的三个数相等时,无法判断最小的数位于哪一边,因此要顺序查找
        if(array[mid]==array[left]&&array[mid]==array[right]){
            return search(array,left, right);
        }
        if(array[mid]>=array[left]){//最小值在右半边
            left = mid;
        }
        else if(array[mid]<=array[right]){//最小值在左半边
            right = mid;
        }
    }
    return array[left];
}
    
public static int search(int[] array, int left, int right){
    for(int i=left+1; i<=right; ++i){
        if(array[i]<array[i-1]){//顺序查找,注意这里也不是完全遍历,而是第一个比之前小的数就是所求
            return array[i];
        }
    }
    return array[left];
}

牛客网答案区很干练的二分查找思路:(仅供参考一般想不到,还是用上面自己想的即可)

采用二分法解答这个问题,
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边
还是右边,这时只好一个一个试 ,
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左
边。因为右边必然都是递增的。
high = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6]
array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid
但情形(1)中low = mid + 1就不会错误

代码:

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

2.Find Minimum in Rotated Sorted Array 2

leetcode154

题目:和153一样,这里新增数组可以有重复数字,如[1,3,3,3]

思路:思路还是一样,就是需要考虑重复的问题,改动如下:

public static int findMin(int[] nums) {
    int len = nums.length;
    if(len==1){
        return nums[0];
    }
    //left、right代表原数组、旋转数组两大阵营,结束条件是相邻
    int left = 0;
    int right = nums.length-1;
    while(left<right){
        int mid = (left+right)/2;
        if(left+1==right){//结束条件
            if(nums[right]<nums[0]){//也可能存在原数组没旋转的情况
                return nums[right];
            }else{
                return nums[0];
            }
        }
        if(nums[mid]==nums[left]&& nums[mid]==nums[right]){
            return search(nums);
        }
        else if(nums[mid]>=nums[left]){//在右半边
            left = mid;
        }
        else if(nums[mid]<=nums[right]){//在左半边
            right = mid;
        }
    }
    return nums[left];
}
    
public static int search(int[] array){
    int min = array[0];
    for(int i=1; i<array.length; ++i){
        min = Math.min(min, array[i]);//顺序查找,注意这里必须完全全部遍历
    }
    return min;
}

24.矩阵中的路径

《剑指offer》p89、leetcode 79

题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

a b c e
s f c s
a d e e

思路:二维矩阵中路径查找问题通常用回溯法。回溯法非常适合由多个步骤组成的问题,每个步骤都有多个选项。它从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案。回溯法可以看成蛮力法的升级版。

public static boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
    if(str==null){
        return true;
    }
    if(matrix==null || matrix.length<str.length){
        return false;
    }
    //路径查找中,不能重复进入相同的格子,因此需要设置标记矩阵,初始为false
    boolean[] visited = new boolean[rows*cols];
    
    for(int i=0; i<rows; ++i){
        for(int j=0; j<cols; ++j){
            int index = i*cols+j;
            if(matrix[index]==str[0]){//如果第一个字符匹配,则开始回溯法匹配路径
                if(isPath(matrix,rows,cols,i,j,str,0, visited)){//匹配成功
                    return true;
                }
            }
        }
    }
    return false;
}
    
public static boolean isPath(char[] matrix, int rows, int cols, int i, int j, char[] str, int strIndex, boolean[] visited){
    if(strIndex==str.length){
        return true;//查找完成,找到路径的结束条件
    }
    int index = i*cols+j;
    if(i<0 || j<0 || i>=rows || j>=cols || visited[index]==true){
        return false;//查找到边界外或已经访问过,false
    }
    if(matrix[index]==str[strIndex]){//如果当前字符匹配,则开始回溯法匹配路径
        visited[index] = true;
        //上下左右回溯法查找
        if(isPath(matrix,rows,cols,i-1,j,str,strIndex+1, visited)
                ||isPath(matrix,rows,cols,i+1,j,str,strIndex+1,visited)
                ||isPath(matrix,rows,cols,i,j-1,str,strIndex+1,visited)
                ||isPath(matrix,rows,cols,i,j+1,str,strIndex+1,visited)){
            return true;
        }
    }
    visited[index] = false;//回溯
    return false;
}

leetcode79的代码:

public static boolean exist(char[][] board, String word) {
    if(board==null || board.length==0 || word==null || word.length()==0){
        return false;
    }
	
    int m = board.length;
    int n = board[0].length;
    if((m*n)<word.length()){
        return false;
    }
    boolean[][] visited = new boolean[m][n];//避免重复访问的标记数组,初始为false
	
    for(int i=0; i<m; ++i){
        for(int j=0; j<n; ++j){
            if(board[i][j]==word.charAt(0)){
                if(isPath(board, m, n, i, j, word, 0, visited))
                    return true;
            }
        }
    }
	
    return false;
}
	
public static boolean isPath(char[][] board, int m, int n, int i, int j, String word, int strIndex, boolean[][] visited){
    if(strIndex==word.length()){
        return true;//查找完成,结束条件
    }
    if(i<0 || j<0 || i>=m || j>=n || visited[i][j]==true || board[i][j]!=word.charAt(strIndex)){
        return false;//剪枝,错误条件:访问越界或已经访问过或当前字符不匹配
    }
    visited[i][j] = true;
    if(isPath(board, m, n, i-1, j, word, strIndex+1, visited)||
            isPath(board, m, n, i+1, j, word, strIndex+1, visited)||
            isPath(board, m, n, i, j-1, word, strIndex+1, visited)||
            isPath(board, m, n, i, j+1, word, strIndex+1, visited)){
        return true;
    }
	
    visited[i][j] = false;//回溯
    return false;
}

25.机器人的运动范围

《剑指offer》p92.

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

思路:通常物体或人在二维矩阵运动这类问题够可以用回溯法解决。

public int movingCount(int threshold, int rows, int cols)
{
    int[] res = new int[]{0};//最多可到达的格子数,存成数组便于参数传递修改
    boolean[][] visited = new boolean[rows][cols];//记录访问过的标记位数组,初始化为false
    return isPath(rows, cols, 0, 0, visited, res, threshold);
}
    
//回溯法矩阵路径查找,返回能到达格子的个数
public static int isPath(int rows, int cols, int i, int j, boolean[][] visited, int[] res, int k){
    if(i<0 || j<0 || i>=rows || j>=cols || visited[i][j]==true || (digitSum(i)+digitSum(j))>k){
        return res[0];//剪枝的结束条件:访问越界或已经访问过或行列数位和大于k
    }
    visited[i][j] = true;
    res[0]++;
    int resTemp;
    resTemp = Math.max(isPath(rows,cols,i-1,j,visited,res,k), res[0]);
    resTemp = Math.max(isPath(rows,cols,i+1,j,visited,res,k), resTemp);
    resTemp = Math.max(isPath(rows,cols,i,j-1,visited,res,k), resTemp);
    resTemp = Math.max(isPath(rows,cols,i,j+1,visited,res,k), resTemp);
    return resTemp;
}
    
//一个数字的数位和
public static int digitSum(int num){
    int sum = 0;
    while(num!=0){
        sum += (num%10);
        num/=10;
    }
    return sum;
}

26.Unique Paths(两道)

1.Unique Paths

leetcode 62 Unique Paths

题目:给定m*n的矩阵,机器人从左上走到右下,每次只能向右或向下,How many possible unique paths are there?

思路:dp问题,dp[i][j]表示走至当前格子时的unique paths数。

方程:dp[i][j]=dp[i-1][j]+dp[i][j-1], dp[0][0]=1

dp问题,自上而下分析上述方程,自下而上循环解决

//dp[i][j]=dp[i-1][j]+dp[i][j-1], dp[0][0]=1
public static int uniquePaths(int m, int n) {
    int[][] dp = new int[m][n];//dp[i][j]表示走至当前格子时的unique paths数
    
    dp[0][0] = 1;
    for(int i=0; i<m; ++i){
        for(int j=0; j<n; ++j){
            if(i==0){//第一行的dp[0][j]=1
                dp[i][j] = 1;
            }
            else{
                if(j==0){//每行的第一个dp[i][0]=dp[i-1][0]
                    dp[i][j] = dp[i-1][j];
                }
                else{//其他的dp[i][j] = dp[i-1][j] + dp[i][j-1]
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }
    }
    
    return dp[m-1][n-1];
}

**优化:**上述方法时间o(mn)、空间o(mn),其实只用o(n)的空间即可,对每行来说只缓存上一行的dp即可

//dp[i][j]=dp[i-1][j]+dp[i][j-1], dp[0][0]=1
public static int uniquePaths(int m, int n) {
    if(m==1 || n==1){
        return 1;
    }
    int[] dp = new int[n];//dp[j]缓存上一行的unique paths数,初始第一行的dp[j]=1
    dp[0] = 1;
	
    for(int i=0; i<m; ++i){
        for(int j=1; j<n; ++j){//直接从第1列开始就行了,第0列不用考虑
            if(i==0){//初始第一行的dp[j]=1
                dp[j] = 1;
            }
            else{
                dp[j] = dp[j-1] + dp[j];//其他的dp[i][j] = dp[i-1][j] + dp[i][j-1]
            }
        }
    }
	
    return dp[n-1];
}

2.Unique Paths 2

leetcode 63 Unique Paths 2

题目:和Unique Paths一样,左上到右下,每次只能向右或者向下,但是新增了障碍物条件,Now consider if some obstacles are added to the grids. How many unique paths would there be?

example:

Input:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
Output: 2

思路:还是一样的。dp问题,用了优化后思路,dp[j]缓存上一行的unique paths数

方程:dp[i][j]=dp[i-1][j]+dp[i][j-1], dp[0][0]=1

dp问题,自上而下分析上述方程,自下而上循环解决

//dp[i][j]=dp[i-1][j]+dp[i][j-1], dp[0][0]=1
public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
    int m = obstacleGrid.length;
    int n = obstacleGrid[0].length;
    if(obstacleGrid[0][0]==1){//如果起始点是障碍物
        return 0;
    }
    int[] dp = new int[n];//dp[j]缓存上一行的unique paths数
    dp[0] = 1;
    
    for(int i=0; i<m; ++i){
        for(int j=0; j<n; ++j){
            if(i==0){//初始第一行的dp[j]=1
                if(obstacleGrid[i][j]!=1){
                    if(j>0){
                        dp[j] = dp[j-1];
                    }
                }
                else{
                    dp[j] = 0;//有障碍物,此路不通
                }
            }
            else{//其他行
                if(obstacleGrid[i][j]!=1){
                    if(j>0){
                        dp[j] = dp[j-1] + dp[j];
                    }
                }
                else{
                    dp[j] = 0;//有障碍物,此路不通
                }
            }
        }
    }
    
    return dp[n-1];
}

27.剪绳子

《剑指offer》p96.

题目:给定一根长度为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。

思路:

问题是求最优解;

整体的问题的最优解是依赖各个子问题的最优解;

子问题之间还有互相重叠的更小的子问题;

为避免子问题的重复计算,我们存储子问题的最优解。从上往下分析问题,从下往上求解问题。

上面的几个条件可以看出,属于动态规划问题。

dp方程:f(n) = max(f(i)*f(n-i))

public static int maxProduct(int n){
    if(n==2){
        return 1;
    }
    if(n==3){
        return 2;
    }
    int[] res = new int[n+1];
    res[1] = 1;
    res[2] = 2;
    res[3] = 3;
    for(int i=4; i<=n; ++i){
        for(int j=1; j<=(i/2); ++j){
            res[i] = Math.max(res[j]*res[i-j], res[i]);//f(n)=max(f(i)*f(n-i))
        }
    }
    
    return res[n];
}

28.不用加减乘除做加法

牛客网、leetcode 371

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

思路:首先看十进制是如何做的: 5+7=12,三步走

第一步:相加各位的值,不算进位,得到2。

第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。

第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。

同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111

第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。

第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。

第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。

继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。

public int Add(int num1,int num2) {
    int temp;
    while(num2!=0){
        temp = num1^num2;//加
        num2 = (num1&num2)<<1;//进位
        num1 = temp;
    }
    return num1;
}

29.Two Sum

leetcode 1

题目:给定数组(非有序)和target,找到nums[i]+nums[j]==target的i、j,假定给定条件一定会有一个解

Example:

Given nums = [2, 7, 11, 15], target = 9,
	
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

思路1:暴力解法,时间o(n2),空间o(1)–38ms

public int[] twoSum(int[] nums, int target) {
    int[] res = new int[2];
    for(int i=0; i<nums.length-1; ++i){
        for(int j=i+1; j<nums.length; ++j){
            if( (nums[i] + nums[j]) == target ){
                res[0] = i;
                res[1] = j;
            }
        }
    }
    return res;
}

思路2:只遍历一次数组,辅助map,temp=target-nums[i],看map中是否有temp,有则返回两者索引即可,没有将当前nums[i]存入map,继续遍历。时间o(n),空间o(n)–5ms

public static int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();//key:nums[i]; value:i
    for(int i=0; i<nums.length; ++i){
        int temp = target-nums[i];
        if(!map.containsKey(temp)){
            map.put(nums[i], i);
        }
        else{
            return new int[]{map.get(temp), i};
        }
    }
    return new int[]{0,0};
}

30.二进制中1的个数

《剑指offer》p100、leetcode191 Number of 1 Bits

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

本题超级技巧:x&(x-1)可以将整数最右边的1变成0

思路:x&(x-1)可以将整数最右边的1变成0,通过这个小技巧,我们只要循环判断n=n&(n-1)是否为0,即可统计1的个数。整数中有多少个1,则循环多少次。

有了这个思路,可以轻松解以下相关题目。位运算相关题目

用一条语句判断一个整数是不是2的整数次方。

if(n&(n-1)==0) return true;

输入两个整数m,n,计算需要改变m的二进制表示中的多少位才能得到n?

int x=m^n; return NumberOf1(x);

解法一:从尾部到最高位(0到32位),依次找1。不好

public static int NumberOf2(int n) {//从尾部到最高位(0到32位),依次找1
    int res = 0;
    int temp = 1;
    int cnt = Integer.toBinaryString(n).length();//返回二进制的位数,这样不用暴力的写32位了
    while(cnt!=0){
        if((n&temp)==temp){
            res++;
        }
        temp=temp<<1;
        cnt--;
    }
    return res;
}

解法二:x&(x-1)可以将整数最右边的1变成0。每次把二进制的最后一个1置为0(二进制数中不存在1时,立刻变0)

public static int NumberOf1(int n) {
    int res = 0;
    while(n!=0){
        n=(n-1)&n; //每次把二进制的最后一个1置为0(二进制数中不存在1时,立刻变0)
        res++;
    }
    return res;
}

31.数值的整数次方

《剑指offer》p110、leetcode 50

题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

思路:记得考虑base==0与exponent<0的情况。解法三更为优化,递归实现将exponent二分下去减少乘法运算次数。判断一个整数为奇数偶数,这里把%运算换成位运算,效率更高(二进制最低位如果为1则为奇数,为0则为偶数)。

超级知识点:注意MIN_VALUE取反的情况。在JDK中,整型类型是有范围的 -2147483648~2147483647(-231-231-1),最大值为Integer.MAX_VALUE,即2147483647,最小值为Integer.MIN_VALUE -2147483648。

Integer.MIN_VALUE取反或者取绝对值呢仍为Integer.MIN_VALUE,因为绝对值2147483648超过Integer.MAX_VALUE 2147483647。

因此有如下重要结论:

​ Math.abs(Integer.MIN_VALUE) = Integer.MIN_VALUE
​ Integer.MAX_VALUE + 1 = Integer.MIN_VALUE

解法一:无耻解法2333

public static double Power(double base, int exponent) {
    return Math.pow(base, exponent);
}

解法二:逐个base相乘即可

public static double Power(double base, int exponent) {
    double res = 1;
    boolean positive = true;
    
    //Math.abs(Integer.MIN_VALUE) =  Integer.MIN_VALUE
    //Integer.MAX_VALUE + 1 = Integer.MIN_VALUE
    if(exponent==Integer.MIN_VALUE){
        exponent = exponent + 2;//防止最小值的负数取反越界,这里加2来个近似(加4也可以但加1不ååå行,必须为偶数参与二分)
    }
    
    if(exponent<0){
        positive = false;
    }
    exponent = Math.abs(exponent);
    while(exponent!=0){
        exponent--;
        res*=base;
    }
    return positive?res:(1/res);
}

解法三,效率更高:

//a^n = a^(n/2)*a^(n/2)
public static double Power(double base, int exponent) {
    double res = 1;
    boolean positive = true;//exponent正负的标记位
	
    //Math.abs(Integer.MIN_VALUE) =  Integer.MIN_VALUE
    //Integer.MAX_VALUE + 1 = Integer.MIN_VALUE
    if(exponent==Integer.MIN_VALUE){
        exponent = exponent + 2;//防止最小值的负数取反越界,这里加2来个近似(加4也可以但加1不行,必须为偶数参与二分)
    }
    if(exponent<0){
        positive = false;
        exponent = -exponent;
    }
	
    while(exponent!=0){
        if((exponent&1)==1){//exponent是奇数
            res*=base;
            exponent--;
        }else{//exponent是偶数,二分,减少乘法次数,效率更高
            base*=base;
            exponent/=2;
        }
    }
    return positive?res:(1/res);
}
	
public static void main(String[] args){
    System.out.println(Power(2, -2147483648));
}

32.打印从1到最大的n位数

《剑指offer》p114

题目:打印从1到最大的n位数(本题是大数问题,当n位数很多时,只能用其他的数据结构来存储我们的非常大的数字)

思路:使用数组,采用数字全排列的方法。如果我们在数字前面补0的话,就会发现n位所有十进制数其实就是n个从0到9的全排列。也就是说,我们把数字的每一位都从0到9排列一遍,就得到了所有的十进制数。当然打印的时候,我们应该不输出左边的0

解法一:递归实现全排列,顺序打印(代码比较简单,但可能会栈溢出)

public class Hello{
    public static void main(String[] args){
        printOneToNBit(3);
    }
    //打印从1到最大的n位数,就是一个n个数的全排列,用递归实现
    static void printOneToNBit(int n){
        int[] number = new int[n]; //n位的数组存放数字的全排列
        for(int i=0; i<10; ++i){
            number[0] = i;
            OneToNBit(number, n, 0);
        }
    }
    //递归实现数字全排列
    static void OneToNBit(int[] number, int n, int index){
        //递归结束条件,已经完成一个数字全排列,打印出来
        if(index == n-1){
            printNumber(number);
            return;
        }
        index++;
        for(int i=0; i<10; ++i){
            number[index] = i;
            OneToNBit(number, n, index);
        }
    }
    //打印一个数组排列,左边的0都不输出
    static void printNumber(int[] number){
        boolean notFirst = false;
        for(int i=0; i<number.length; ++i){
            if(number[i]!=0){
                notFirst = true;
            }
            if(notFirst){
                System.out.print(number[i]);
            }
        }
        System.out.println();
    }
	
}

解法二:字符串存数字,采用循环,不断加1。这个思路在两个数相加(大数问题)等题目中可以用到。

//打印从1到最大的n位数
public static void printOneToNBit(int n){
    //大数问题,用字符串来存数字
    StringBuilder num = new StringBuilder();
    for(int i=0; i<n; ++i){
        num.append("0");
    }
    
    while(!addOne(num)){
        printNum(num);//字符串每次加1,然后打印,直到n位的最大数
    }
}
    
//字符串每次加1,然后打印,直到n位的最大数
public static boolean addOne(StringBuilder num){
    int cnt = 0;//前一位的进位
    for(int i=num.length()-1; i>=0; --i){
        int temp = num.charAt(i)-'0'+cnt;//第i位上的数字
    
        if(i==num.length()-1){//个位加1
            temp++;
        }
        if(temp==10){//如果有进位,操作如下
            if(i==0){//如果当前是最高位且有进位,说明已经到达是最大数,return true
                return true;
            }
            num.setCharAt(i, '0');//进位后当前位为0
            cnt = 1;//进位1
        }
        else{//如果没进位
            num.setCharAt(i, (char)('0'+temp));
            break;//没有进位了,跳出循环即可
        }
    }
    
    return false;
}
    
//打印数字,左边的0不打印
public static void printNum(StringBuilder num){
    boolean leftCnt = true;
    for(int i=0; i<num.length(); ++i){
        char temp = num.charAt(i);
        if(temp!='0') {
            leftCnt = false;
        }
        if(!leftCnt){
            System.out.print(temp);
        }
    }
    System.out.println();
}
    
public static void main(String[] args){
    printOneToNBit(3);
}

33.删除链表中重复的节点(两道)

1.Remove Duplicates from Sorted List((保留一个重复节点)---leetcode83
2.Remove Duplicates from Sorted List 2(不保留重复节点)---《剑指offer》p122、leetcode82

1.Remove Duplicates from Sorted List

leetcode 83

题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点保留一个,返回链表头指针。

Example:

Input: 1->1->2->3->3
Output: 1->2->3

代码:

public ListNode deleteDuplicates(ListNode head) {
    if(head==null || head.next==null){
        return head;
    }
    
    ListNode cur = head;
    
    while(cur.next!=null){
        if(cur.next.val==cur.val){
            cur.next = cur.next.next;
        }else{
            cur = cur.next;
        }
    }
    
    return head;
}

2.Remove Duplicates from Sorted List II

《剑指offer》p122、leetcode82

题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路:

1.需要两个指针,一个指向前一个节点pre,另一个指向当前节点p。如果遇到相等的节点,p向后移动,pre不动,存下此时相等数值tempVal方便后面的比较,直到遇到p和p.next不相等,pre就可以指向当前的p。

2.注意:链表开头可能就开始有重复的节点,所以设置一个哨兵guard(0),

public static  ListNode deleteDuplication(ListNode pHead)
{
    //链表为空或只有一个节点,返回pHead
    if(pHead==null || pHead.next==null){
        return pHead;
    }
    
    //设置哨兵
    ListNode guard = new ListNode(0);
    guard.next = pHead;
    
    ListNode pre = guard;//重复节点的前一个节点
    ListNode cur = guard.next; //用于遍历的节点
    
    //遍历链表
    while(cur!=null){
        if(cur.next!=null && cur.next.val==cur.val){//后一节点==当前节点
            int temp = cur.val;//存储重复的值,用于后边比较
            while(cur!=null && cur.val==temp){//如果重复
                cur = cur.next;
            }
            pre.next = cur;
        }
        else{//后一节点!=当前节点
            pre = pre.next;
            cur = cur.next;
        }
    }
    
    return guard.next;
}

34.正则表达式匹配

《剑指offer》p124、leetcode 10

题目:请实现一个函数用来匹配包括’.‘和’*'的正则表达式。*表示前面字符0~无穷个,.表示任意一个字符。要求全部,匹配,不是部分匹配。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

思路:二维DP问题。这道题的核心是分析’*’,DP[i][j]代表计算str[:i]与patten[:j]匹不匹配.最终要得到的结果就是dp[s.length()][p.length()],转移方程如下(时间复杂度O(m*n),空间复杂度O(n)):

dp[i][j] = dp[i - 1][j - 1], 如果s[i] == p[j] || p[j] == '.'

=dp[i][j - 2], 如果s[i] != p[j] && p[j] == '*' && s[i] != p[j - 1](只能匹配0次)

=dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j], 如果s[i] != p[j] && p[j] == '*' && s[i] == p[j - 1](匹配0\1\多次)

返回:
dp[s.length()][p.length()]

代码:

//正则匹配. *。思路:二维DP。
public boolean isMatch(String s, String p) {
    if(s == null || p == null) return false;
    boolean[][] dp = new boolean[s.length()+1][p.length()+1];
    dp[0][0] = true;
    for(int i = 0; i < p.length(); i++){
        if (p.charAt(i) == '*' && dp[0][i-1]) {
            dp[0][i+1] = true;
        }
    }
    for(int i = 0; i < s.length(); i++){
        for(int j = 0; j < p.length(); j++){
            if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.'){
                dp[i+1][j+1] = dp[i][j];
            }
            if(p.charAt(j) == '*'){
                if(s.charAt(i) != p.charAt(j-1) && p.charAt(j-1) != '.'){
                    dp[i+1][j+1] = dp[i+1][j-1];
                }else{
                    dp[i+1][j+1] = dp[i+1][j-1] || dp[i+1][j] || dp[i][j+1];
                }
            }
        }
    }
    return dp[s.length()][p.length()];
}

public static void main(String[] args){
    char[] str = new char[]{'a','a','a'};
    char[] pattern = new char[]{'a', 'b', '*', 'a', 'c', '*', 'a'};
    char[] str2 = new char[]{'a','b'};
    char[] pattern2 = new char[]{'.','*','c'};
    String s = "ab";
    String p = ".*c";//这对测试用例解决了一个大bug,很好

    System.out.println(isMatch(s,p));
}

35.表示数值的字符串

《剑指offer》p127、leetcode 65

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

思路:+A.Be+C,对字符串分别判断A、B、C部分是否存在且是否正确,都正确则返回true。注意边界条件:

1.整数部分判断,整数部分可以没有,但没有则必须:

有小数部分,小数点+小数部分-->  -.123正确, -.错误 
没有小数点,也没有小数部分-->  -正确

2.判断小数部分,小数部分前面如果有整数则小数部分可以没有,小数部分前面如果没有整数则必须要有小数部分(-.123正确, -.错误 .5正确 .错误, 100.正确)

3.判断e部分,如果有e部分则e有且必须有后面的整数,同时有且必须有前面的数字(e9是错误的, .e9是正确的)

public class Solution{
    //+A.Be+C
    public static boolean isNumeric(char[] str) {
        if(str==null || str.length==0){
            return false;
        }
	
        //遍历字符串
        int index = 0;
	
        //1.正负号判断
        if(str[index]=='+' || str[index]=='-'){
            index++;
        }
	
        //2.整数部分判断(整数部分可以没有 但没有则必须要有小数部分,-.123正确, -.错误)
        boolean num = false;//是否有整数部分
        int indexTemp = readNum(str, index);
        if(indexTemp>index){//有整数部分
            num = true;
        }
        index = indexTemp;
	
        //3.判断小数部分,小数部分前面如果有整数则小数部分可以没有,小数部分前面如果没有整数则必须要有小数部分(-.123正确, -.错误 .5正确 .错误, 100.正确)
        if(index<str.length && str[index]=='.'){
            index++;
            indexTemp = readNum(str, index);
            if(!num && indexTemp==index){//小数部分前面如果没有整数则必须要有小数部分
                return false;
            }
            index = indexTemp;
            num = true;
        }
	
        //4.判断e部分,如果有e部分则e有且必须有后面的整数,同时有且必须有前面的数字(e9是错误的, .e9是正确的)
        if(index<str.length && (str[index]=='e'||str[index]=='E') ){
            if(!num){//e部分前面没有数字,false (e9错误)
                return false;
            }
            index++;
            if(index==str.length){//有e但没有后面的整数部分,false
                return false;
            }
            if(str[index]=='+' || str[index]=='-'){
                index++;
            }
            indexTemp = readNum(str, index);
            if(indexTemp==index){//有e但没有后面的整数部分,false
                return false;
            }
            index = indexTemp;
        }
	
        if(index!=str.length){//如果都判断完了,字符串还有字符,false
            return false;
        }
        return true;
    }
	
    //遍历读取数字部分,返回不是数字的索引
    public static int readNum(char[] str, int index){
        while(index<str.length){
            int temp = str[index]-'0';
            if(temp<0 || temp>9){//不是数字
                return index;
            }
            else{//是数字
                index++;
            }
        }
        return index;
    }
    public static void main(String[] args){
        String s1 = ".5";//Expected:true
        String s2 = ".";//Expected:false
        String s = "100.";//Expected:true
        String s4 = "-";//Expected:true
        System.out.println(isNumeric(s4.trim().toCharArray()));
    }
}

36.调整数组顺序使奇数位于偶数前面(3道)

《剑指offer》p129(需要保持顺序不变)
leetcode 905(不用保持顺序)
leetcode 328(调整链表的奇偶顺序)

《剑指offer》p129(需要保持顺序不变)

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

思路:遍历一遍数组,记下奇数偶数个数;建立新数组temp后,再遍历一遍原数组,奇数偶数分开放到新数组temp中;将新数组temp的值赋给原数组array。时间o(n),空间o(n)

public static void reOrderArray(int [] array) {
    if(array==null || array.length<2){
        return;
    }
    int cntOdd = 0;//记录奇数个数
    for(int i: array){//遍历一遍数组,记下奇数偶数个数
        if((i&1)==1){
            cntOdd++;
        }
    }
    int oddIndex = 0;//奇数index
    int evenIndex = cntOdd;//偶数index
    int[] tempArray = new int[array.length];
    for(int i=0; i<array.length; ++i){//再遍历一遍数组,奇数偶数分开放到新数组temp中
        if((array[i]&1)== 1){
            tempArray[oddIndex++] = array[i];
        }
        else{
            tempArray[evenIndex++] = array[i];
        }
    }
    
    for(int i=0; i<array.length; ++i){//再遍历一遍数组,将新数组temp的值赋给原数组array
        array[i] = tempArray[i];
    }
}

Sort Array By Parity

leetcode 905

题目:偶数在前,奇数在后,不用保持顺序

Example :

Input: [3,1,2,4]
Output: [2,4,3,1]
The outputs [4,2,3,1], [2,4,1,3], and [4,2,1,3] would also be accepted.

思路:既然不用保持顺序,那么就不用空间o(n)的辅助了,思路:遍历一遍数组,两个指针变量,partition思想

//偶数在前,奇数在后,不用保持顺序。思路:遍历一遍数组,两个指针变量,partition思想
public static int[] sortArrayByParity(int[] A) {
    if(A==null || A.length<2){
        return A;
    }
	
    int storeEven = 0;//遍历一遍数组,把偶数放在前面。storeEven左边都是偶数
    for(int i=0; i<A.length; ++i){
        if((A[i]&1)==0){
            swap(A, storeEven++, i);
        }
    }
    return A;
}
public static void swap(int[] A, int i, int j){
    int temp = A[i];
    A[i] = A[j];
    A[j] = temp;
}

Odd Even Linked List

leetcode 328

题目:Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.

You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.

题目大意:奇数号节点在前,偶数号节点在后,同时要求时间o(n),空间o(1)

Example 1:

Input: 1->2->3->4->5->NULL
Output: 1->3->5->2->4->NULL	

Example 2:

Input: 2->1->3->5->6->4->7->NULL
Output: 2->3->6->7->1->5->4->NULL

思路:拆分成两个链表,奇数号链表、偶数号链表,最后再合成即可

public ListNode oddEvenList(ListNode head) {
    if(head==null || head.next==null || head.next.next==null){
        return head;
    }
    //奇数号链表
    ListNode oddHead = head;
    ListNode oddP = oddHead;
    
    //偶数号链表
    ListNode evenHead = head.next;
    ListNode evenP = evenHead;
    
    //遍历并拆分原链表
    ListNode cur = oddP.next.next;
    oddP.next = null;
    evenP.next = null;
    ListNode temp;
    while(cur!=null && cur.next!=null){
        //奇数号节点
        temp = cur;
        cur = cur.next;
        oddP.next = temp;
        oddP = oddP.next;
        oddP.next = null;
    
        //偶数号节点
        temp = cur;
        cur = cur.next;
        evenP.next = temp;
        evenP = evenP.next;
        evenP.next = null;
    }
    if(cur!=null){//如果还剩一个奇数号节点
        oddP.next = cur;
        oddP = oddP.next;
        oddP.next = null;
    }
    
    //连接奇偶两个链表
    oddP.next = evenHead;
    return oddHead;
}

37.链表中倒数第k个结点(两道)

《剑指offer》p134--返回倒数第k个结点
《左神》p35==leetcode19--删除倒数第k个结点

1.返回倒数第k个结点

《剑指offer》p134

题目:输入一个链表,输出该链表中倒数第k个结点。

思路:双指针,遍历一遍链表即可。时间o(n),空间o(1)

public static ListNode FindKthToTail(ListNode head,int k) {
    if(head==null || k<=0){
        return null;
    }
    ListNode right = head;//先出发,走k-1个节点
    ListNode left = head;//后出发,直到right.next为null时left即为所求
    
    //1.right先走k-1个节点
    while(k!=1){
        right = right.next;
        if(right==null){
            return null;//链表的节点数都不够k个,更没有倒数第k个节点了
        }
        k--;
    }
    
    //2.left、right一起走,直到right.next==null
    while(right.next!=null){
        left = left.next;
        right = right.next;
    }
    return left;
}

2.删除倒数第k个节点

《左神》35==leetcode19,思路差不多,删除时应该首先遍历到倒数第k+1个节点,且需要设置哨兵

//删除倒数第n个节点,则需要找到倒数第n+1个节点,需要设置哨兵
public ListNode removeNthFromEnd(ListNode head, int n) {
    if(head==null || n<=0){
        return null;
    }
	
    //1.设置哨兵
    ListNode guard = new ListNode(0);
    guard.next = head;
	
    //2.找到倒数第n+1个节点
    ListNode right = guard;//先出发,走n-1个节点
    ListNode left = guard;//后出发,直到right.next为null时left即为所求
	
    //1.right先走n个节点
    while(n!=0){
        right = right.next;
        n--;
    }
	
    //2.left、right一起走,直到right.next==null,此时left指向倒数第n+1个节点
    while(right.next!=null){
        left = left.next;
        right = right.next;
    }
	
    //3.删除倒数第k个节点
    left.next = left.next.next;
    return guard.next;
}

38.判断链表是否有环(两道)

1.判断链表是否有环

leetcode 141

题目:Given a linked list, determine if it has a cycle in it.

思路:每次fast走两步,low走一步,如果两者相遇则有环,如果遇到null则无环

public boolean hasCycle(ListNode head) {
    if(head==null){
        return false;
    }
    //判断链表是否有环。思路:两个指针,一个走两步,一个走一步,如果能遇上则有环
    ListNode right = head;//走两步
    ListNode left = head;//走一步
    while(right!=null && right.next!=null){
        right = right.next.next;
        left = left.next;
        if(right==left){//如果相遇,则有环
            return true;
        }
    }
    return false;
}

2.判断链表是否有环,有则返回环的入口结点

《剑指offer》p139、leetcode 142

题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

思路:

1.判断链表是否有环。思路:两个指针,一个走两步,一个走一步,如果能遇上则有环

如果有环:前后指针相遇的地方一定在环中,此时遍历这个环计算环的节点数k,之后从头遍历链表,前指针比后指针多走k步,前后指针再次相遇时即为为环的入口节点

class ListNode {
    int val;
    ListNode next = null;
	
    ListNode(int val) {
        this.val = val;
    }
}
	
//给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
public ListNode EntryNodeOfLoop(ListNode pHead)
{
    if(pHead==null){
        return null;
    }
    //1.判断链表是否有环。思路:两个指针,一个走两步,一个走一步,如果能遇上则有环
    ListNode right = pHead;//走两步
    ListNode left = pHead;//走一步
    boolean haveCycle = false; //是否有环
    while(right!=null && right.next!=null){
        right = right.next.next;
        left = left.next;
        if(right==left){//如果相遇,则有环
            haveCycle = true;
            break;
        }
    }
    if(!haveCycle){//如果没环,返回null
        return null;
    }
	
    //2.有环,找出环的入口节点。
    //  思路:2.1遍历环计算环中节点个数k个;
    //       2.2从头遍历链表,两个指针,一个先走k,两个一起走直到相遇即为入口节点
	
    //2.1遍历环计算环中节点个数k个
    int cycleNum = 1; //环中节点个数
    while(right.next!=left){
        cycleNum++;
        right = right.next;
    }
	
    //2.2从头遍历链表,两个指针,一个先走cycleNum-1个节点,两个一起走直到相遇时left即为入口节点
    right = pHead;
    left = pHead;
    while(cycleNum!=1){//right先走cycleNum-1个节点
        right = right.next;
        cycleNum--;
    }
	
    while(right.next!=left){//right、left两个一起走,直到相遇时的left节点即为所求
        right = right.next;
        left = left.next;
    }
    return left;
}

39.两个单链表相交的第一个公共节点(2道)

两个链表的第一个公共节点(无环)--《剑指offer》p253、leetcode 160
两个链表的第一个公共节点(可能有环,需自己判断)--《左神》62,掌握这一道题就够了

1.两个链表的第一个公共节点(无环)

《剑指offer》p253、leetcode 160

题目:输入两个链表,链表无环,找出它们的第一个公共结点。如果不相交返回null

思路:

法一:因为要从两个链表的尾部往前一一判断,因此需要借助两个栈的辅助。java栈的实现可以用LinkedList。时间o(m+n),空间o(m+n)

法二(更简单的办法):链表长度差。首先遍历两个链表得到他们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个结点。在第二次遍历的时候,在较长的链表上先走若干步,接着再同时在两个链表上遍历,找到第一个相同的结点就是他们的第一个公共结点。时间o(m+n),空间o(1)

//返回两个无环链表相交的第一个节点,如果不相交返回null
public static ListNode FindFirstNoCycleCommonNode(ListNode head1, ListNode head2){
    if(head1==null || head2==null){
        return null;
    }
    //1.计算两个链表的长度
    int len1 = 1;
    int len2 = 1;
    ListNode p1 = head1;
    ListNode p2 = head2;
    while(p1.next!=null){
        len1++;
        p1 = p1.next;
    }
    while(p2.next!=null){
        len2++;
        p2 = p2.next;
    }
    if(p1!=p2){//两个链表的最后一个节点不相同,说明不相交
        return null;
    }
    //2.计算长度差k,让长链表先走k步
    ListNode longList = len1>=len2?head1:head2;
    ListNode shortList = longList==head1?head2:head1;
    int k = Math.abs(len1-len2);
    while(k!=0){
        longList = longList.next;
        k--;
    }
    //3.两个链表一起走,直到找到相交的第一个节点
    while(longList!=null && shortList!=null){
        if(longList==shortList){
            return longList;
        }
        longList = longList.next;
        shortList = shortList.next;
    }
    return null;//不相交
}

2.两个链表的第一个公共节点(可能有环,需自己判断)

《左神》62,掌握这一道题就够了

题目:返回两个链表的第一个公共节点(可能有环,需自己判断),没有的话返回null

思路:

1.判断两个链表是否有环

1.1 若一个链表有环,另一个链表无环,则不可能相交,返回null
1.2 都无环则判断两个无环链表的第一个公共节点,没有返回null

2.若有环,判断两个有环链表的第一个公共节点。

2.1找两个有环链表的入口节点p1/p2,若p1==p2则交点在发生在环前,与无环链表公共节点求法类似(长度差,分别先后走找交点)
	
2.2若p1!=p2交点发生在环中,从p1开始遍历环,如果遇到p2则找到交点即为p2,否则没有交点返回null

代码:

class ListNode {
    int val;
    ListNode next = null;
	
    ListNode(int val) {
        this.val = val;
    }
}
public class Solution{
    /**
     * 题目:返回两个链表的第一个公共节点(可能有环,需自己判断),没有的话返回null
     * 思路:
     *   1.判断两个链表是否有环,都无环则判断两个无环链表的第一个公共节点,没有返回null
     *   2.判断两个有环链表的第一个公共节点。
     *     2.1找两个有环链表的入口节点p1/p2,若p1==p2则交点在发生在环前,与无环链表公共节点求法类似(长度差,分别先后走找交点)
     *     2.2若p1!=p2交点发生在环中,从p1开始遍历环,如果遇到p2则找到交点即为p2,否则没有交点返回null
     */
    public ListNode FindFirstCommonNode(ListNode head1, ListNode head2) {
        if(head1==null || head2==null){
            return null;
        }
	
        ListNode beginNodeOfCycle1 = hasCycle(head1);//链表head1是否有环,无环返回null,有环返回入口节点
        ListNode beginNodeOfCycle2 = hasCycle(head2);//链表head2是否有环,无环返回null,有环返回入口节点
        //1.两个链表都无环,判断两个无环链表的第一个公共节点,没有返回null
        if (beginNodeOfCycle1==null && beginNodeOfCycle2==null) {
            return noCycleCommonNode(head1, head2);
        }
	
        //2.两个链表都有环
        if (beginNodeOfCycle1!=null && beginNodeOfCycle2!=null) {
            //2.1beginNodeOfCycle1==beginNodeOfCycle2,交点在发生在环前,与无环链表公共节点求法类似(长度差,分别先后走找交点)
            if(beginNodeOfCycle1==beginNodeOfCycle2){
                ListNode p1 = head1;
                ListNode p2 = head2;
                int len1 = 0;
                int len2 = 0;
                while(p1!=beginNodeOfCycle1){
                    len1++;
                    p1 = p1.next;
                }
                while(p2!=beginNodeOfCycle2){
                    len2++;
                    p2 = p2.next;
                }
                ListNode longHead = len1>=len2 ? head1 : head2;
                ListNode shortHead = longHead==head1 ? head2 : head1;
                int k = Math.abs(len1-len2);
	
                while(k!=0){//长链表先走k步
                    longHead = longHead.next;
                    k--;
                }
                while(longHead!=beginNodeOfCycle1 && shortHead!=beginNodeOfCycle2){//两个链表一起走
                    if(longHead==shortHead){//找到公共节点
                        return longHead;
                    }
                    longHead = longHead.next;
                    shortHead = shortHead.next;
                }
            }
            //2.2beginNodeOfCycle11=beginNodeOfCycle2,交点发生在环中,从p1开始遍历环,如果遇到p2则找到交点即为p2,否则没有交点返回null
            else{
                while(beginNodeOfCycle1.next!=beginNodeOfCycle1){
                    beginNodeOfCycle1 = beginNodeOfCycle1.next;
                    if(beginNodeOfCycle1==beginNodeOfCycle2){//环中找到了第一个公共节点,返回
                        return beginNodeOfCycle1;
                    }
                }
                return null;//环中没有公共节点,即两个有环链表独立不相交的
            }
        }
	
        //一个链表有环,一个链表无环,返回null
        return null;
    }
	
    //判断一个链表是否有环,没环返回null,如果有环返回入口节点
    public ListNode hasCycle(ListNode pHead){
        ListNode p1 = pHead;//每次走两步
        ListNode p2 = pHead;//每次走一步
        boolean hasCycle = false;
        while(p2.next!=null && p1.next.next!=null){
            p2 = p2.next.next;
            p1 = p1.next;
            if(p1==p2){
                hasCycle = true;//两个指针相遇则有环
                break;
            }
        }
        if(hasCycle){//链表有环,返回入口节点
            //1.计算环的节点个数
            ListNode temp = p1;
            int k = 1;
            while(p1.next!=temp){
                k++;
                p1 = p1.next;
            }
	
            //2.right指针从链表头先走k步
            ListNode right = pHead;
            ListNode left = pHead;
            while(k!=0){
                right = right.next;
                k--;
            }
	
            //3.left从头和right一起走,直到相遇
            while(left!=right){
                left = left.next;
                right = right.next;
            }
            return left;//找到入口节点并返回
        }
        return null;//链表无环,返回null
    }
	
    //判断两个无环链表的第一个公共节点,没有则返回null
    public ListNode noCycleCommonNode(ListNode head1, ListNode head2){
        //长度差,一个先走k步,然后同时走,看是否有公共节点
        if(head1==null || head2==null){
            return null;
        }
        ListNode p1 = head1;
        ListNode p2 = head2;
        int len1 = 1;
        int len2 = 1;
        while(p1.next!=null){
            len1++;
            p1 = p1.next;
        }
        while(p2.next!=null){
            len2++;
            p2 = p2.next;
        }
        if(p1!=p2){//两个链表的最后一个节点不等,则没有公共节点,返回null
            return null;
        }
	
        ListNode longHead = len1>=len2 ? head1 : head2;
        ListNode shortHead = longHead==head1 ? head2 : head1;
        int k = Math.abs(len1-len2);
	
        while(k!=0){//长链表先走k步
            longHead = longHead.next;
            k--;
        }
	
        while(longHead!=null && shortHead!=null){//两个链表一起走
            if(longHead==shortHead){//找到公共节点
                return longHead;
            }
            longHead = longHead.next;
            shortHead = shortHead.next;
        }
        return null;//没有公共节点,返回null
    }
}

40.合并两个排序的链表

《剑指offer》p145、leetcode21、《左神》84

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

思路:正常的遍历两个链表结点,每次将较小的节点插到新链表的尾部即可。设置哨兵

//合并两个有序的链表,设置一个哨兵
public ListNode Merge(ListNode list1,ListNode list2) {
    ListNode guard = new ListNode(0);
    ListNode p = guard;
    ListNode temp;
    while(list1!=null && list2!=null){
        if(list1.val<=list2.val){
            temp = list1;
            list1 = list1.next;
    
        }
        else{
            temp = list2;
            list2 = list2.next;
        }
        temp.next = null;
        p.next = temp;
        p = p.next;
    }
    if(list1!=null){
        p.next = list1;
    }
    if(list2!=null){
        p.next = list2;
    }
    return guard.next;
}

41.树的子结构

《剑指offer》p148、leetcode 572

题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

思路:分为两步。第一步,先序遍历树A,在树A中找到和树B的根节点值相同的节点R;第二步,判断树A中以R为根节点的子树是不是包含和树B一样的结构

class TreeNode {
     int val = 0;
     TreeNode left = null;
     TreeNode right = null;
	
     public TreeNode(int val) {
        this.val = val;
     }
}
	
//两个二叉树A、B,判断B是否是A的子结构。思路:先序遍历A,找到与B的根节点相同的节点,判断子结构是否相同
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
    if(root1==null || root2==null){
        return false;
    }
	
    if(root1.val==root2.val){
        if(isSame(root1, root2)){
            return true;
        }
    }
	
    return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
public boolean isSame(TreeNode root1, TreeNode root2){
    if(root2==null){
        return true;
    }
    if(root1==null){
        return false;
    }
	
    if(root1.val==root2.val){
        return isSame(root1.left, root2.left) && isSame(root1.right, root2.right);
    }
    return false;
}

42.二叉树的翻转(镜像)与对称(2道)

《剑指offer》p157--二叉树的翻转(转换成镜像二叉树)
leetcode 101、《剑指offer》p159--判断二叉树是否是对称

1.二叉树的翻转(镜像)

《剑指offer》p157

题目:操作给定的二叉树,将其变换为源二叉树的镜像。

思路:前序遍历树的每一个结点,对每个节点的左右子树互换,递归下去即可。

//操作给定的二叉树,将其变换为源二叉树的镜像。思路:左右子树互换,递归下去即可
public void Mirror(TreeNode root) {
    if(root==null){
        return;
    }
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;
    
    Mirror(root.left);
    Mirror(root.right);
}

2.对称的二叉树

《剑指offer》p159、leetcode 101

题目:判断一颗二叉树是不是对称的

For example, this binary tree [1,2,2,3,4,4,3] is symmetric:

    1
   / \
  2   2
 / \ / \
3  4 4  3
	

思路:根左右与根右左的递归判断即可。

从根开始,先判断其左右孩子,如果都不存在则为true

如果只有一个为空或者两个指向的val值不同则为false

当根的左右孩子节点相同时,再判断其左孩子的左孩子和右孩子的右孩子、左孩子的右孩子和右孩子的左孩子

//判断一颗二叉树是不是对称的。思路:根左右与根右左的递归判断即可
public boolean isSymmetrical(TreeNode pRoot)
{
    if(pRoot==null){
        return true;
    }
    if(pRoot.left==null && pRoot.right==null){
        return true;
    }
    if(pRoot.left!=null && pRoot.right!=null){
        return isSymmetricalDetail(pRoot.left, pRoot.right);
    }
    return false;
}
public boolean isSymmetricalDetail(TreeNode root1, TreeNode root2){
    if(root1==null && root2==null){
        return true;
    }
    if(root1==null || root2==null){
        return false;
    }
    if(root1.val==root2.val){
        return isSymmetricalDetail(root1.left, root2.right) && isSymmetricalDetail(root1.right, root2.left);
    }
    return false;
}

43.顺时针打印矩阵

《剑指offer》p161、leetcode 54 spiral Matrix(螺旋矩阵)

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

思路:先定义好打印一圈的函数,然后根据矩阵大小确定需要打印几圈。

import java.util.ArrayList;
public class Solution{
    //顺时针打印矩阵。思路:先定义打印一圈的函数,再确定打印几圈
    public static ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
        if(matrix==null){
            return res;
        }
        int m = matrix.length;
        int n = matrix[0].length;
	
        int startI = 0;
        int startJ = 0;
        int endI = m-1;
        int endJ = n-1;
        int cycleNum = (int)Math.ceil((double)Math.min(m,n)/2);//一共打印cycleNum圈
	
        while(cycleNum!=0){//循环打印cycleNum圈
            printOneCycle(matrix, startI, startJ, endI, endJ, res);
            startI++;
            startJ++;
            endI--;
            endJ--;
            cycleNum--;
        }
        return res;
    }
    //定义打印一圈的函数。限定一圈的边界需要两个参数,圈的左上角元素和右下角元素的坐标
    public static void printOneCycle(int[][] matrix, int startI, int startJ, int endI, int endJ, ArrayList<Integer> res){
        //1.打印圈的上边,每圈都有
        int temp = startJ;
        while(temp<=endJ) {
            res.add(matrix[startI][temp]);
            temp++;
        }
        //2.打印圈的右边,必须两行以上
        temp = startI+1;
        while(endI>startI && temp<=endI){
            res.add(matrix[temp][endJ]);
            temp++;
        }
        //3.打印圈的下边,必须两行以上、两列以上
        temp = endJ-1;
        while(endI>startI && endJ>startJ && temp>=startJ){
            res.add(matrix[endI][temp]);
            temp--;
        }
        //4.打印圈的左边,必须三行以上、两列以上
        temp = endI - 1;
        while(endI-1>startI && endJ>startJ && temp>startI){
            res.add(matrix[temp][startJ]);
            temp--;
        }
    }
    public static void main(String[] args){
        int[][] matrix = new int[][]{{1,2,3,4},
                                    {5,6,7,8},
                                    {9,10,11,12},
                                    {13,14,15,16}};
        System.out.println(printMatrix(matrix));
    }
}

44.包含min函数的栈

《剑指offer》p165、leetcode155

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))

思路:如果只用一个元素保存最小的元素,那么当最小的元素被弹出后,就没有办法得到剩余元素中最下的元素。因此引入辅助栈,每次将最小元素(之前的最小元素和新压入的元素两者的较小者)都保存起来放到辅助栈里。

import java.util.Stack;
	
public class Solution {
	
    Stack<Integer> stk = new Stack<>();
    Stack<Integer> minStk = new Stack<>(); //辅助栈,每次push时都压入当前最小值(node与minStk栈顶元素中的较小者)
    public void push(int node) {
        stk.push(node);
        if(minStk.empty() || node < minStk.peek()){
            minStk.push(node);
        }
        else{
            minStk.push(minStk.peek());
        }
    }
	
    public void pop() {
        stk.pop();
        minStk.pop();
    }
	
    public int top() {
        return stk.peek();
    }
	
    public int min() {
        return minStk.peek();
    }
}

45.栈的压入、弹出序列

《剑指offer》p168、leetcode 946

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

思路:借助一个辅助栈来模拟入栈弹栈过程。每次都入栈一个数字,并且循环比较(栈顶与出栈序列当前元素),如果相等:弹栈继续下一次循环(直到出栈序列遍历完返回true);如果不等:入栈后面的数字(直到入栈序列遍历完还没找到则false)

//每次都入栈一个数字,并且循环比较(栈顶与出栈序列当前元素),如果相等:弹栈继续下一次循环;如果不等:入栈后面的数字
public static boolean IsPopOrder(int [] pushA,int [] popA) {
    Stack<Integer> stack = new Stack<>();
    int len = pushA.length;
    int pushAIndex = 0;
    int popAIndex = 0;
    while(popAIndex<len){
        if(stack.isEmpty() || popA[popAIndex]!=stack.peek()){
            if(pushAIndex==len){ //直到入栈序列遍历完还没找到则false
                return false;
            }
            stack.push(pushA[pushAIndex]);
            pushAIndex++;
        }else{
            if(!stack.isEmpty()){
                stack.pop();
                popAIndex++;
            }
        }
    }
    return true;//直到出栈序列遍历完返回true
}

46.二叉树的层序遍历(3道)

二叉树的层序遍历--《剑指offer》p171
二叉树的层序遍历,按行打印--《剑指offer》p174、leetcode 102、leetcode107
二叉树的层序遍历,之字形按行打印--《剑指offer》p176、leetcode 103

1.二叉树的层序遍历

《剑指offer》p171

题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:借助队列实现层序遍历

//二叉树的层序遍历。思路:辅助队列
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()){
        TreeNode temp = queue.poll();
        res.add(temp.val);
        if(temp.left!=null){
            queue.offer(temp.left);
        }
        if(temp.right!=null){
            queue.offer(temp.right);
        }
    }
    return res;
}

2.二叉树的层序遍历,按行打印

《剑指offer》p174、leetcode 102、leetcode107

题目:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路:在既有层序遍历上添加两个变量,分别记录当前层的节点数和下一层的节点数。

//二叉树的层序遍历,每一层输出一行。思路:辅助队列,两个变量来计数当前层和下一层的节点个数curCnt\nextCnt
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    if(pRoot==null){
        return res;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(pRoot);
    int curCnt = 1;
    int nextCnt = 0;
    ArrayList<Integer> resTemp = new ArrayList<>();
    while(!queue.isEmpty()){
        TreeNode temp = queue.poll();
        resTemp.add(temp.val);
        curCnt--;
	
        if(temp.left!=null){
            queue.offer(temp.left);
            nextCnt++;
        }
        if(temp.right!=null){
            queue.offer(temp.right);
            nextCnt++;
        }
	
        if(curCnt==0){
            res.add(resTemp);
            resTemp = new ArrayList<>();
            curCnt = nextCnt;
            nextCnt = 0;
        }
    }
    return res;
}

3.二叉树的层序遍历,之字形按行打印

《剑指offer》p176、leetcode 103

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

思路:在既有层序遍历上添加两个变量,分别记录当前层的节点数和下一层的节点数;且辅助奇偶行的标志位,奇数行从左到右,偶数行从右到左(Collections.reverse即可)

//二叉树的层序遍历,每一层输出一行。思路:辅助队列,两个变量来计数当前层和下一层的节点个数curCnt\nextCnt
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    if(pRoot==null){
        return res;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(pRoot);
    int curCnt = 1;
    int nextCnt = 0;
    ArrayList<Integer> resTemp = new ArrayList<>();
    boolean odd = true; //奇偶行的标志位,奇数行从左到右,偶数行从右到左(Collections.reverse即可)
    while(!queue.isEmpty()){
        TreeNode temp = queue.poll();
        resTemp.add(temp.val);
        curCnt--;
    
        if(temp.left!=null){
            queue.offer(temp.left);
            nextCnt++;
        }
        if(temp.right!=null){
            queue.offer(temp.right);
            nextCnt++;
        }
    
        if(curCnt==0){
            if(!odd){//当前行是偶数行,从右往左,reverse下
                Collections.reverse(resTemp);
            }
            res.add(resTemp);
            resTemp = new ArrayList<>();
            curCnt = nextCnt;
            nextCnt = 0;
            odd = !odd;//下一行的奇偶是!odd
        }
    }
    return res;
}

47.递归与非递归实现二叉树前序、中序、后序遍历

《左神》88、 leetcode144、94、145

题目:递归与非递归实现二叉树前序、中序、后序遍历

1.前序:

//递归实现前序遍历
ArrayList<Integer> res = new ArrayList<>();
public ArrayList<Integer> preorderTraversal(TreeNode root) {
	if(root==null){
		return res;
	}
	res.add(root.val);
	if(root.left!=null){
		res = preorderTraversal(root.left);
	}
	if(root.right!=null){
		res = preorderTraversal(root.right);
	}
	return res;
}
	
//非递归实现前序遍历
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if(root==null){
        return res;
    }
    Stack<TreeNode> stack = new Stack<>();//定义栈
    TreeNode temp = root;
    while(temp!=null || !stack.isEmpty()){
        if(temp!=null){
            res.add(temp.val);//压栈之前先访问
            stack.push(temp);
            temp = temp.left;
        }
        else{
            temp = stack.pop().right;
        }
    }
	return res;
}

2.中序

//递归实现中序遍历
ArrayList<Integer> res = new ArrayList<>();
public ArrayList<Integer> inorderTraversal(TreeNode root) {
    if(root==null){
        return res;
    }
    if(root.left!=null){
        res = inorderTraversal(root.left);
    }
    res.add(root.val);
    if(root.right!=null){
        res = inorderTraversal(root.right);
    }
    return res;
}//非递归实现中序遍历
public List<Integer> inorderTraversal(TreeNode root) { 
    List<Integer> res = new ArrayList<>();
    if(root==null){ 
        return res;
    }
    Stack<TreeNode> stack = new Stack<>();
    TreeNode temp = root;
    while(temp!=null || !stack.isEmpty()){
        if(temp!=null){
            stack.push(temp);
            temp = temp.left;
        }
        else{
            temp = stack.pop();
            res.add(temp.val);
            temp = temp.right;
        }
    }
	return res;
}

3.后序

//递归实现后序遍历
ArrayList<Integer> res = new ArrayList<>();
public ArrayList<Integer> postorderTraversal(TreeNode root){
    if(root==null){
        return res;
    }
    if(root.left!=null){
        res = postorderTraversal(root.left);
    }
    if(root.right!=null){
        res = postorderTraversal(root.right);
    }
    res.add(root.val);
    return res;
}
    
//非递归实现后序遍历。思路:1.根右左压栈,利用中间栈output来存储逆后序遍历的结果 + 2.最后再一起输出即可
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if(root==null){
        return res;
    }
    Stack<TreeNode> stack = new Stack<>();//定义栈,存根节点
    Stack<TreeNode> output = new Stack<>();//还需要一个辅助栈来存储逆后序遍历的结果
    TreeNode temp = root;
    while(temp!=null || !stack.isEmpty()){
        if(temp!=null){
            stack.push(temp);
            output.push(temp);
            temp = temp.right;
        }
        else{
            temp = stack.pop().left;
        }
    }
    while(!output.isEmpty()){
        res.add(output.pop().val);
    }
    return res;
}

48.判断数组是否为二叉搜索树的后续遍历序列

《剑指offer》p179.

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

思路:后序遍历序列是左右根,所以先取数组中最后一个数,作为根节点。

1.找根
2.找根左右的左右分界线(从左往右第一个比根大的即为右)
3.右可以没有,如果有右,则右中不含比根小的,否则false
4.递归判断下去

代码:

//题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果,假设输入的数组的任意两个数字都互不相同。
/**
 * 思路:
 *   1.找根
 *   2.找根左右的左右分界线(从左往右第一个比根大的即为➡右)
 *   3.右可以没有,如果有右,则右中不含比根小的,否则false
 *   4.递归判断下去
 */public boolean VerifySquenceOfBST(int [] sequence) {if(sequence==null || sequence.length==0){return false;}if(sequence.length==1){return true;}return VerifySquenceOfBSTDetail(sequence, 0, sequence.length-1);}public boolean VerifySquenceOfBSTDetail(int[] sequence, int start, int end){if(start>end){return false;}if(start==end){return true;}//1.找根int root = sequence[end];
​
​        //2.找左右分界线int rightIndex = start;int temp = start;while(temp<end){if(sequence[temp]>root){//第一个比root大的节点即为右
​                rightIndex = temp;break;}
​            temp++;}if(temp==end){//只有左,没有右,则递归判断左即可return VerifySquenceOfBSTDetail(sequence, start, end-1);}//3.有右,则右中不能含比根小的,否则返回false
    temp = rightIndex;
    while(temp<end){
        if(sequence[temp]<root){//右中含比根小的,返回false
            return false;
        }
        temp++;
    }

    //4.递归判断左、右序列即可
    if(rightIndex==start){//只有左,没有右,递归判断右即可
        return VerifySquenceOfBSTDetail(sequence, rightIndex, end-1);
    }
    else{//有左有右
        return VerifySquenceOfBSTDetail(sequence, start, rightIndex-1) && VerifySquenceOfBSTDetail(sequence, rightIndex, end-1);
    }
}

49.二叉树中和为指定值的路径(2道)

leetcode 112--二叉树中和为指定值的一条路径
《剑指offer》p182、leetcode 113--二叉树中和为指定值的所有路径(回溯法)

1.Path Sum

leetcode 112

题目:二叉树中和为指定值的一条路径。

思路:正常递归遍历即可。

//打印出二叉树中结点值的和为输入整数的满足条件的一条路径。
List<Integer> temp = new ArrayList<>();
public boolean hasPathSum(TreeNode root, int sum) {
    if(root==null){
        return false;
    }
    sum -= root.val;
    if(root.left==null && root.right==null && sum==0){//当前节点为叶节点且路径和==sum,true
        return true;
    }
    return hasPathSum(root.left,sum) || hasPathSum(root.right, sum);
}

2.Path Sum 2

《剑指offer》p182、leetcode 113

题目:二叉树中和为指定值的所有路径。输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

思路:回溯法。遍历树每一条到叶子节点的路径,找寻符合条件的路径。到叶子节点如果还没有找到路径,就要回退到父节点继续寻找,依次类推。

//打印出二叉树中结点值的和为输入整数的满足条件的所有路径。思路:递归+回溯法
ArrayList<ArrayList<Integer>> res = new ArrayList<>();//存放所有路径
ArrayList<Integer> tempPath = new ArrayList<>();//存放当前路径
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
    if(root==null){
        return res;
    }
    target -= root.val;
    tempPath.add(root.val);
    if(root.left==null && root.right==null && target==0){//当前节点是叶节点且路径和==target,满足条件
        res.add(new ArrayList<>(tempPath));
    }
    FindPath(root.left, target);
    FindPath(root.right, target);
    tempPath.remove(tempPath.size()-1);//回溯点
    return res;
}

50.复杂链表的复制

《剑指offer》p187、《左神》56、leetcode138

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路:分而治之,复杂问题分成小问题一一解决。时间o(n) 空间o(1)
1、遍历链表并复制结点,复制的结点在相应节点之后:a->b->c->d->e为a->a’->b->b’->c->c’->d->d’->e->e’
2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
3、拆分链表,将链表拆分为原链表和复制后的链表

class RandomListNode {
    public int label;
    public RandomListNode next, random;
    public RandomListNode(int x) {
        this.label = x;
    }
};
public class Solution2 {
    public RandomListNode copyRandomList(RandomListNode head) {
        if(head==null){
            return null;
        }
	
        RandomListNode p = head;
        //1.遍历链表,复制节点
        while(p!=null){
            RandomListNode newNode = new RandomListNode(p.label);
            newNode.next = p.next;
            p.next = newNode;
            p = p.next.next;
        }
	
        //2.遍历链表,复制random指针
        p = head;
        while(p!=null){
            if(p.random!=null){
                p.next.random = p.random.next;
            }
            p = p.next.next;
        }
        
        //3.拆分链表,将复制链表拆出来
        p = pHead;
        RandomListNode newHead = new RandomListNode(0);//新链表的哨兵
        RandomListNode newP = newHead;
        while(p!=null){
            RandomListNode temp = p.next;
            p.next = p.next.next;
            temp.next = null;
            newP.next = temp;
            newP = newP.next;
            p = p.next;
        }
	
    return newHead.next;
    }
}

51.二叉搜索树与双向链表(两道)

1.二叉搜索树转双向链表

《剑指offer》p191、leetcode426、《左神》74

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

思路:中序遍历+记录有序链表的最后一个节点。二叉搜索树的中序遍历是有序,且遍历到根节点时左子树形成的链表已经是有序,因此需要辅助保存当前链表最后一个节点。

class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;
    public TreeNode(int x){
        val = x;
        this.left = null;
        this.right = null;
    }
}
public class Solution2 {
    public TreeNode Convert(TreeNode root){
        if(root==null){
            return null;
        }
        TreeNode lastPointer = null;//当前有序双向链表的最后一个节点
        lastPointer = ConvertDetail(lastPointer, root);
        while(lastPointer.left!=null){
            lastPointer = lastPointer.left;
        }
        return lastPointer;
    }
    //中序遍历,返回当前有序链表的最后一个节点。二叉搜索树的中序遍历是有序,且遍历到根节点时左子树形成的链表已经是有序的了
    private TreeNode ConvertDetail(TreeNode lastPointer, TreeNode root){
        if(root==null){
            return lastPointer;
        }
        if(root.left!=null){
            lastPointer = ConvertDetail(lastPointer, root.left);
        }
        root.left = lastPointer;
        if(lastPointer!=null){
            lastPointer.right = root;
        }
        lastPointer = root;
        if(root.right!=null){
            lastPointer = ConvertDetail(lastPointer, root.right);
        }
        return lastPointer;
    }
}

2.有序数组转平衡二叉树

leetcode 108

题目:Given an array where elements are sorted in ascending order, convert it to a height balanced BST.

For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

思路:数组中间的数做root,递归二分即可。

//有序数组转成平衡二叉树。思路:数组中间的数做root,递归二分即可
public TreeNode sortedArrayToBST(int[] nums) {
    if(nums==null || nums.length==0){
        return null;
    }
    return sortedArrayToBSTDetail(nums, 0, nums.length-1);
}
public TreeNode sortedArrayToBSTDetail(int[] nums, int start, int end){
    if(start<0 || end>=nums.length || start>end){
        return null;
    }
    int mid = (start+end)/2;
    TreeNode root = new TreeNode(nums[mid]);
    
    root.left = sortedArrayToBSTDetail(nums, start, mid-1);
    root.right = sortedArrayToBSTDetail(nums, mid+1, end);
    return root;
}

52.序列化二叉树

《剑指offer》p194、leetcode 297(序列化反序列化二叉树)、leetcode 449(序列化反序列化平衡二叉树)

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

思路:序列化:前序遍历,遇到子节点为空用"$”代替;反序列化:按照前序遍历序列化的顺序进行反推。

//请实现两个函数,分别用来序列化和反序列化二叉树
//1.序列化:前序遍历,遇到空节点为空用"null"代替
public String serialize(TreeNode root) {
    StringBuilder res = new StringBuilder();
    serializeDetail(root, res);
    return res.toString();
}
//这样分开的话,递归的时候不用保存全局变量res,防止二叉树太深导致的递归栈溢出
public void serializeDetail(TreeNode root, StringBuilder res){
    if(root==null){
        res.append("null,");
        return;
    }
    res.append(root.val + ",");
    serializeDetail(root.left, res);
    serializeDetail(root.right, res);
}
    
//2.反序列化:按照前序遍历序列化的顺序进行反推,将String转成ArrayList<String>方便删除同时节省空间
public TreeNode deserialize(String data) {
    if(data==null || data.length()==0){
        return null;
    }
    String[] str = data.split(",");
    ArrayList<String> strr = new ArrayList<>(Arrays.asList(str));
    return deserializeDetail(strr);
}
public TreeNode deserializeDetail(ArrayList<String> strr){
    if(strr.size()==0){
        return null;
    }
    String temp = strr.get(0);
    strr.remove(0);
    if(temp.equals("null")){
        return null;
    }
    TreeNode root = new TreeNode(Integer.valueOf(temp));
    root.left = deserializeDetail(strr);
    root.right = deserializeDetail(strr);
    return root;
}

53.全排列/组合问题(五道)

1.字符串的全排列(abc,acb...)--《剑指offer》p197
2.无重复数字的全排列--leetcode 46
3.有重复数字的全排列--leetcode 47
4.八皇后问题--《剑指offer》p200(返回摆法总数)、leetcode 52 N-Queens 2(返回摆法总数)、leetcode 51 N-Queens(返回所有的摆法)
5.字符串s1的全排列是否在s2字符串中--leetcode 567

1.字符串的全排列

《剑指offer》p197

题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

例如输入字符串abc,则打印由字符a,b,c所能排列出来的所有字符串:abc,acb,bac,bca,cab,cba。注意aac aac只能有一个,需要过滤重复

思路:递归。我们求整个字符串的排列,可以看成两步:首先求出所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符交换。第二步固定第一个字符,求后面所有字符的排列。这个时候我们仍把后面的所有字符分成两部分:后面字符的第一个字符,以及这个字符之后的所有字符。然后把第一个字符逐一和它后面的字符交换……

//字符串的全排列
public ArrayList<String> Permutation(String str) {
    ArrayList<String> res = new ArrayList<>();
    if(str==null || str.length()==0){
        return res;
    }
    PermutationDetail(res, str.toCharArray(), 0);
    Collections.sort(res);//最后排序一下,按字典序输出
    return res;
}
    
public void PermutationDetail(ArrayList<String> res, char[] str, int index){
    if(index==str.length){//递归出口
        String temp = String.valueOf(str);
        if(!res.contains(temp)){//过滤重复
            res.add(String.valueOf(str));
        }
        return;
    }
    for(int i=index; i<str.length; ++i){
        swap(str, i, index);//每次递归将index位置字符与后面所有字符进行分别交换
        PermutationDetail(res, str, index+1);//当前位置交换后进行下一位上的排列递归
        swap(str, i, index);
    }
}
public void swap(char[] str, int i, int j){
    char temp = str[i];
    str[i] = str[j];
    str[j] = temp;
}

2.无重复数字的全排列

leetcode 46

题目:和字符串的全排列一样,换汤不换药。给定一个无重复数字的序列,返回这些数所能排列出所有序列。

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
	

代码:

//数字的全排列
public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    if(nums==null || nums.length==0){
        return res;
    }
    permuteDetail(res, nums, 0);
    return res;
}
public void permuteDetail(List<List<Integer>> res, int[] nums, int index){
    if(index==nums.length){//递归出口
        Integer[] temp = new Integer[nums.length];
        for(int i= 0;i<nums.length;i++){
            temp[i]= nums[i];//int->Integer
        }
        res.add(Arrays.asList(temp));
    }
    for(int i=index; i<nums.length; ++i){
        swap(nums, i, index);//每次递归将index位置字符与后面所有字符进行分别交换
        permuteDetail(res, nums, index+1);//当前位置交换后进行下一位上的排列递归
        swap(nums, i, index);
    }
}
public void swap(int[] nums, int i, int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

3.有重复数字的全排列

leetcode 47

题目:给定一个有重复数字的序列,返回这些数所能排列出所有序列。注意需要把重复的全排列给过滤掉

思路:有重复的数字的全排列。为了过滤重复,对于同一个值,只交换一次,否则跳过。为了保证这一点,辅助hash表来过滤重复的元素,如果重复则跳过不交换即可。

//有重复数组的全排列。需要过滤重复,思路:用hash即可,不用排序
public List<List<Integer>> permuteUnique(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    if(nums==null || nums.length==0){
        return res;
    }
    permuteUniqueDetail(res, nums, 0);
    return res;
}
public void permuteUniqueDetail(List<List<Integer>> res, int[] nums, int index){
    if(index==nums.length){//递归出口
        Integer[] itg = new Integer[nums.length];
        for(int i=0; i<nums.length; ++i){//int->Integer
            itg[i] = nums[i];
        }
        List<Integer> temp = Arrays.asList(itg);
        res.add(temp);
    }

    Set<Integer> used = new HashSet<>();//hashSet记录已经访问过的数,避免重复排列
    for(int i=index; i<nums.length; ++i){
        if(used.add(nums[i])){//对于同一个值,只交换一次,否则跳过。
            swap(nums, index, i);
            permuteUniqueDetail(res, nums, index+1);
            swap(nums, index, i);
        }
    }
}
public void swap(int[] nums, int i, int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

4.八皇后问题

《剑指offer》p200(返回摆法总数)、leetcode 52 N-Queens 2(返回摆法总数)、leetcode 51 N-Queens(返回所有的摆法)

题目:在n*n的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法或者输出八皇后摆法。

思路:八皇后问题(不能同一行、列、对角线)。思路:转化为数字的全排列问题。总体思路: 0-n全排列的总数 - check(全排列出现同时在对角线上的)。

详细思路:八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行的皇后的列号。先把ColumnIndex的八个数字分别用0-7初始化,接下来我们要做的事情就是对数组ColumnIndex做全排列。由于我们是用不同的数字初始化数组中的数字,因此任意两个皇后肯定不同列。我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-jColumnIndex[i]-Column[j]或者j-iColumnIndex[i]-ColumnIndex[j]。

//八皇后问题(不能同一行、列、对角线)。思路:转化为数字的全排列问题:76548321->皇后分别在:第一行的7,第二行的6...
public static List<List<String>> solveNQueens(int n) {
    List<List<String>> res = new ArrayList<>();
    if(n<1){
        return res;
    }
    int[] nums = new int[n];//将八皇后问题转成不重复数字全排列问题
    for(int i=0; i<n; ++i){//0-n个数字全排列之前初始化
        nums[i] = i;
    }
    solveNQueensDetail(res, nums, 0);//全排列并且判断是否满足八皇后,满足的排列存入结果
    return res;
}
//全排列并判断是否满足八皇后(不能出现在同一对角线)
public static void solveNQueensDetail(List<List<String>> res, int[] nums, int index){
    if(index==nums.length){//递归出口
        if(!check(nums)){//不满足八皇后(同一对角线)
            return;
        }
        List<String> tempList = new ArrayList<>();
        for(int i=0; i<nums.length; ++i){//将八皇后排列转成输出格式-->..Q.
            StringBuilder sb = new StringBuilder();
            int cnt = nums[i];
            int ct = 0;
            while(ct<cnt){
                sb.append(".");
                ct++;
            }
            sb.append("Q");
            ct++;
            while(ct<nums.length){
                sb.append(".");
                ct++;
            }
            tempList.add(sb.toString());
        }
        res.add(tempList);
        return;
    }
    for(int i=index; i<nums.length; ++i){
        swap(nums, index, i);
        solveNQueensDetail(res, nums, index+1);
        swap(nums, index, i);
    }
}
public static void swap(int[] nums, int i, int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}
//判断是否同一对角线
public static boolean check(int[] nums){//检查一种排列是否出现在同一对角线,是则不是八皇后排列
    for(int i=0; i<nums.length-1; ++i){
        for (int j=i+1; j<nums.length; ++j){
            if( Math.abs(i-j)==Math.abs(nums[i]-nums[j]) ){
                return false;
            }
        }
    }
    return true;
}

5.字符串s1的全排列是否在s2字符串中

leetcode 567 Permutation in String

题目:Given two strings s1 and s2, write a function to return true if s2 contains the permutation of s1. In other words, one of the first string’s permutations is the substring of the second string.

Input:s1 = "ab" s2 = "eidbaooo"
Output:True
Explanation: s2 contains one permutation of s1 ("ba").
	

思路:

方法1:最简单最暴力的方法其实就是找到s1的所有全排列,然后在s2中查找是否这些全排列字符串在s2中。但是这种方法耗时太大,会导致超时

方法2:滑动窗口
其实不需要找到s1的全排列,因为我们只需要考虑s2中是否包含s1中同样个数的字符,并且这些字符是连在一起的就行了。因此,我们可以使用一个滑动窗口,在s2上滑动。在这个滑动窗口中的字符及其个数是否刚好等于s1中的字符及其个数,此外滑动窗口保证了这些字符是连在一起的。

具体思路:辅助两个map键值对来模拟滑动窗口中的字符情况,由于都是小写字母,不用map,直接用int[26]来模拟map即可

注意:判断两个数组是否相等Arrays.equals(int[] num1, int[] num2);

public static boolean checkInclusion(String s1, String s2) {
    if(s1==null || s1.length()==0){
        return true;
    }
    if(s2==null || s2.length()==0 || s1.length()>s2.length()){
        return false;
    }
    int[] map1 = new int[26];//s1的字符键值对
    int[] map2 = new int[26];//s2上滑动窗口的字符键值对
    for(int i=0; i<s1.length(); ++i){//记录s1的字符map
        map1[s1.charAt(i)-'a']++;
    }
    for(int i=0; i<s2.length(); ++i){//遍历s2,滑动窗口
        map2[s2.charAt(i)-'a']++;
        if((i+1)>=s1.length()){//滑动窗口
            if(Arrays.equals(map1, map2)){//如果两个map包含的字符一样,则找到了s2中的s1的全排列
                return true;
            }
            else{//否则,滑动窗口map2删除最左边的元素
                map2[s2.charAt(i-s1.length()+1)-'a']--;
            }
        }
    }
    return false;
}

54.打印出给定字符串中字符的所有组合

题目:“abc”->打印a,b,c,ab,ac,bc,abc

思路:在字符串位置的index到chs.length()-1中找number个数,组合成字符串放在list中,每次递归到chs[index]字符时,都有两种选择:

1.放进组合,在begin+1到chs.length()-1中找number-1个数;

2.不放进组合,在begin+1到chs.length()-1中找number个数

public static void combine(char[] chs){
    if(chs==null || chs.length==0){
        return;
    }
    if(chs.length==1){
        System.out.println(chs[0]);
    }
    List<Character> tempList = new ArrayList<>();
    for(int i=1; i<=chs.length; ++i){//组合数字的个数[1~chs.length]
        combineDetail(chs, i, 0, tempList);
    }
}
//从index开始找combineNum个数字的组合,并打印
public static void combineDetail(char[] chs, int combineNum, int index, List<Character> tempList){
    if(combineNum==0){ //找到一种number个字符的组合,将list->String打印出来并回溯
        System.out.println(tempList.toString());
        return;
    }
    if(index==chs.length){//递归出口: 找完number个字符的所有组合,返回
        return;
    }

    //当前位置的数字放进组合,则往后需要找combineNum-1个数
    tempList.add(chs[index]);
    combineDetail(chs, combineNum-1, index+1, tempList);

    //当前位置的数字不放进组合,则往后需要找combineNum个数
    tempList.remove(Character.valueOf(chs[index]));//回溯
    combineDetail(chs, combineNum, index+1, tempList);
}

public static void main(String[] args) {
    char[] nums = new char[]{'a', 'b', 'c'};
    combine(nums);
}
    
out:

[a]
[b]
[c]
[a, b]
[a, c]
[b, c]
[a, b, c]

55.大/小根堆–优先队列实现(两道)

找出数组中最小的k个数(优先队列大根堆)--《剑指offer》p209
找出数组中第k大的数(优先队列小根堆)--leetcode 215
	

注:java优先队列默认小根堆

1.找出数组中最小的k个数(优先队列大根堆)

《剑指offer》p209

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

思路:辅助一个k长的优先队列大根堆,遍历数组的同时每次用大根堆替换k长容器的值(适合海量不能修改数组的数据)—>o(nlogk)。大根堆的实现用优先队列大根堆

//找出数组中最小的k个数(优先队列大根堆)。思路:用优先队列的大根堆。时间o(nlogk),空间o(k)
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
    if(input==null || input.length==0 || k<1 || k>input.length){
        return new ArrayList<>();
    }

    //由于java默认小根堆,所以定义一个k长的大根堆
    Queue<Integer> queue = new PriorityQueue<>(k, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });

    for (int i=0; i<input.length; ++i){
        if(i<k){
            queue.offer(input[i]);
        }
        else{
            if(queue.peek()>input[i]){//把大的数都出堆,留下最小的k个数
                queue.poll();
                queue.offer(input[i]);
            }
        }
    }
    return new ArrayList<>(queue);//treeSet->ArrayList<Integer>
}

2.找出数组中第k大的数(优先队列小根堆)

leetcode 215 Kth Largest Element in an Array

题目:Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.You may assume k is always valid, 1 ≤ k ≤ array’s length.

Example 1:

Input: [3,2,1,5,6,4] and k = 2
Output: 5
	

思路:辅助用优先队列的小根堆,而不是大根堆。时间o(nlogk),空间o(k)

//返回数组中第k大的数,k总是有效的。思路:用优先队列的小根堆,而不是大根堆。时间o(nlogk),空间o(k)
public int findKthLargest(int[] nums, int k) {
    Queue<Integer> queue = new PriorityQueue<>();//默认小根堆
    for (int i=0; i<nums.length; ++i){
        if(i<k){
            queue.offer(nums[i]);
        }
        else{
            if(queue.peek()<nums[i]){//注意这里要比较一下,不满足就不用出堆
                queue.poll();
                queue.offer(nums[i]);
            }
        }
    }
    return queue.poll();
}

56.数据流中的中位数

《剑指offer》p209、leetcode 295

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

思路:对于数据流,对应的就是在线算法了,一道很经典的题目就是在1亿个数中找到最大的前100个数,这是一道堆应用题,找最大的前100个数,那么我们就创建一个大小为100的最小化堆,每来一个元素就与堆顶元素比较,因为堆顶元素是目前前100大数中的最小数,前来的元素如果比该元素大,那么就把原来的堆顶替换掉。那么对于这一道题呢?如果单纯的把所有元素放到一个数组里,每次查找中位数最快也要O(n),综合下来是O(n^2)的复杂度。
我们可以利用上面例子中的想法:动态查找考虑堆,中位数左边的数比它小,右边的数比它大。故用一个最大堆实现左边的数据容器,用一个最小堆实现右边的数据容器,因为要找中位数所以也要保证两边容器的数据个数差不超过1。

java堆的实现用优先队列

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
	
public class Solution {
	
    //左边的大根堆,存比中位数小的数
    private Queue<Integer> bigHeap = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1;
        }
    });
    //右边的小根堆,存比中位数大的数
    private Queue<Integer> smallHeap = new PriorityQueue<>();
	
    //优先放左边的大根堆,左边的大根堆与右边的小根堆的size只能差1
    public void Insert(Integer num) {
        if( bigHeap.size()==smallHeap.size() ){//放左边大根堆
            if(!smallHeap.isEmpty() && smallHeap.peek()<num) {//如果num比右边的小根堆的peek大,应该交换下放到右边
                bigHeap.offer(smallHeap.poll());
                smallHeap.offer(num);
            }else{
                bigHeap.offer(num);
            }
        }
        else if( bigHeap.size()>smallHeap.size() ){//放右边小根堆
            if(bigHeap.peek()>num){//如果num比左边的大根堆的peek小,应该交换下放到左边
                smallHeap.offer(bigHeap.poll());
                bigHeap.offer(num);
            }else{
                smallHeap.offer(num);
            }
        }
    }
	
    public Double GetMedian() {
        if( bigHeap.size()>smallHeap.size() ){
            return (double)bigHeap.peek();
        }
        else{
            return  ((double)bigHeap.peek() + (double)smallHeap.peek()) / 2;
        }
    }
	
	
    public static void main(String[] args){
        Solution s = new Solution();
        int[] nums = new int[]{5,2,3,4,1,6,7,0,8};
        for(int i=0; i<nums.length; ++i){
            s.Insert(nums[i]);
        }
        System.out.println(s.GetMedian());
    }
}

57.数组中最大连续子序列的和

《剑指offer》p218、leetcode 53

题目:给一个数组,返回它的最大连续子序列的和

思路:

法一:动态规划。DP[i]表示以i结尾的数组,目前最大连续子序列的和是DP[i]。递推公式:DP[i] = max{DP[i-1] + A[i],A[i]}

法二:更高效。动态规划需要额外数组存,所以本题改进不用额外的空间,直接辅助两个变量遍历一遍数组即可。两个变量:tempMax记录数组中每一段和大于0的连续子数组,整个数组有很多个这样的tempMax;最后的resultMax是这些tempMax最大的

//有负数的数组中,连续子序列的最大和为多少。思路:辅助两个变量,resMax,tempMax,当tempMax<=0时,tempMax=0
public int FindGreatestSumOfSubArray(int[] array) {
    int resMax = array[0];
    int tempMax = 0;
    for(int i=0; i<array.length; ++i){
        tempMax += array[i];
        resMax = Math.max(resMax, tempMax);
        if(tempMax<=0){//当tempMax<=0,那当前这段连续子数组就不要了,tempMax重置为0
            tempMax = 0;
        }
    }
    return resMax;
}

58.1到n整数中1出现的次数

《剑指offer》p221、leetcode 233

题目:求1 到 n 中1出现的次数。e.g.1~13的整数中1出现的次数:1、10、11、12、13因此共出现5次

思路:按位进行讨论计算。对数字的每一位单独拿出讨论,按数位计算每一位上1出现的次数,将一个数n分成round\weight\former\base,按公式计算即可。

计算规则:

​若weight为0,则1出现次数为round*base
若weight为1,则1出现次数为round*base+former+1
​若weight大于1,则1出现次数为round*base+base
​
​```

详细请参考[从1到n整数中1出现的次数:O(logn)算法](https://blog.csdn.net/yi_afly/article/details/52012593)

​```java
//求出1到n出现1的次数。思路:按数位计算每一位上1出现的次数,将一个数n分成round\weight\former\base,按公式计算即可
public static int NumberOf1Between1AndN_Solution(int n) {
    int res = 0;

    int round = n;
    int weight;
    int former;
    int base = 1;
    while(round!=0){
        weight = round%10;
        round /= 10;
        former = n%base;
        res += round*base;//res += weight*base

        if(weight==1){//res += former + 1
            res += former + 1;
        }
        else if(weight>1){//res += base
            res += base;
        }
        base *= 10;
    }

    return res;
}

59.正整数序列中的第n个数字

《剑指offer》p221、leetcode 400

题目:在整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …中找到第 n个数字,n是正数且在32为整形范围内(n < 231)。e.g.第5位是5,第13位是1

思路:一位数0-9数字在序列中占10位,两位数10-99数字在序列中占290=180位,三位数100-999有3900=2700位。当n=1001时,n-10-180=811>0 && n-10-180-2700<0,且811=3*270+1,说明第n位在三位数的100之后第270个数字即370的中间一位,即7。

//正整数序列中的第n个数字。思路:
public static int findNthDigit(int n) {
    if(n<10){
        return n;
    }
    int digitLen = 2; //当前区间的位数,如:10~99的2
    int start = 10; //当前区间的起始数,如:10~99的10
    int base = 90; //当前区间的所有位数,如10~99的90
    n -= 9;
    while(n!=0){
        int objectNum = (int)Math.ceil((double)n/digitLen) + start -1;//定位到了目标数字
        if(objectNum<(start*10)){//目标数字就在当前区间,这样判断是为了防止int溢出
            int loc = n%digitLen; //定位到目标数字的第几位,从左往右
            if(loc==0){
                loc = digitLen-1;
            }
            else{
                loc = loc-1;
            }
            String num = String.valueOf(objectNum);
            return num.charAt(loc)-'0';
        }
        else{//目标数字在下一个区间
            n -= (base*digitLen);
            digitLen++;
            start *= 10;
            base *= 10;
        }
    }
    return -1;
}

60.把数组排成最小的数

《剑指offer》p227、leetcode 179

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

注意:

我们要判断一个数字的最高位上的数字,故最方便的方法是:数字转成字符串。

且数字拼接过程很可能大数溢出int类型,更要用字符串拼接比较

思路:

1.先把数组中的整数转换成字符串,2.然后用compareTo中定义比较规则,3.并根据该规则调用库函数sort()排序,4.最后把排序后数字字符串依次打印出来即为所求最小的数字。时间复杂度就是排序的时间复杂度o(nlogn)

//把数组排成最小的数
public static String PrintMinNumber(int [] numbers) {
    //1.int[] --> String[]
    String[] strings = new String[numbers.length];
    for(int i=0; i<numbers.length; ++i){
        strings[i] = numbers[i]+"";
    }

    //2.定义字符串排序的比较规则
    Comparator<String> com = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            String s1 = o1 + o2;
            String s2 = o2 + o1;
            return s1.compareTo(s2);
        }
    };

    //3.对String[]排序
    Arrays.sort(strings, com);

    //4.拼接字符串
    StringBuilder sb = new StringBuilder();
    for(int i=0; i<strings.length; ++i){
        sb.append(strings[i]);
    }
    if(sb.charAt(0)=='0'){//"0000"-->"0"
        return "0";
    }
    return sb.toString();
}

61.求把一个数字翻译成不同字符串的个数(DP)

《剑指offer》p231、leetcode 91

题目: 求把数字翻译成字符串的个数。0->a,1->b…11->l,25->z。求一个数字有多少种不同的翻译方法。e.g.258->2,58、25,8

题目分析:这道题要求解码方法,跟之前那道 Climbing Stairs 爬梯子问题 非常的相似,但是还有一些其他的限制条件,比如说一位数时不能为0,两位数不能大于26,其十位上的数也不能为0,除去这些限制条件,跟爬梯子基本没啥区别,也勉强算特殊的斐波那契数列,当然需要用动态规划DP来解。

思路:DP问题。f(i)为第i位开始到最右边结束的数字的翻译个数。–>动态规划题,解题分析步骤如下。

1.从左往右DP分析:定义f(i)为从i至字符串s最右边结束的不同字符串的个数,因此所求为f(0)。f(i) = f(i+1) + f(i+2)(f(i+2)要建立在[i,i+1]是个字母的情况下)

2.详细的DP方程: f(i) =

0,   s[i]==‘0’
f(i+2),   s[i+1]==‘0’ && (s[i]==‘1’ || s[i]==‘2’)
0,   s[i+1]==‘0’ && (else)
f(i+1)+f(i+2),   s[i+1]!='0' && s[i]==‘1’ || (s[i]==‘2’ && s[i+1]-‘0’<=6)
f(i+1),     s[i+1]!='0' && (else)
	

且递归结束条件为:

i==s.length  return 1;
s[i]=='0'  return 0;

3.从右往左循环实现 递归实现存在很多重复计算,因此从右往左循环计算,大问题化成小问题

代码一:按dp方程直接翻译的递归代码

//求把一个数字翻译成不同字符串的个数
public static int numDecodings(String s) {
    if(s==null || s.length()==0){
        return 0;
    }
    return numDecodingsDetail(s.toCharArray(), 0);
}
//从右往左,每次递归表示从String s中的index至最右边字符串结束,可翻译成不同字符串的个数
//f(i) = f(i+1) + f(i+2)(f(i+2)要建立在[i,i+1]是个字母的情况下)
public static int numDecodingsDetail(char[] chs, int index){
    if(index<chs.length && chs[index]=='0'){
        return 0;
    }
    if(index<chs.length-1){
        if(chs[index+1]=='0'){//后一个为0,必须与后一个连成字母,不满足为0
            if(chs[index]=='1' || chs[index]=='2'){//与后一个连成字母
                return numDecodingsDetail(chs,index+2);
            }
            else{//匹配错误,返回0
                return 0;
            }
        }
        else{//后一个不为0,可以不与后一个连成字母,同时如果连成字母需要判断
            if(chs[index]=='1' || (chs[index]=='2' && chs[index+1]<='6')){//自成一派+与后一个连成字母
                return numDecodingsDetail(chs, index+1) + numDecodingsDetail(chs,index+2);
            }
            else {//不能与后一个连成字母,只能自成一派
                return numDecodingsDetail(chs, index+1);
            }
        }
    }
    return 1;//index==chs.length 递归结束
}
    
代码二:递归实现存在很多重复计算,因此从右往左循环计算,大问题化成小问题(这个代码是按照剑指offer的题目来的,A-0,Z-25//求把一个数字翻译成不同字符串的个数
public static int numDecodings(String s) {
    if(s==null || s.length()==0){
        return 0;
    }
    //1.把数字每一位拆分,存到数组中
    char[] chs = s.toCharArray();

    //2.从右往左,res[]存放动态规划循环计算的每一个f(i)结果,最后要返回的是res[0]
    int[] res = new int[chs.length];
    //每次计算f(i)时的临时计数器
    int tempCount = 0;
    for(int i=chs.length-1; i>=0; --i){//从右往左循环计算
        if(i==chs.length-1) {
            tempCount = 1; //如果是最右边的数字时,初始化翻译个数=1
        }
        else{//f(i) = f(i+1)
            tempCount = res[i+1];
        }
        //f(i) = f(i+1) + g(i,i+1)*f(i+2)
        if(i<chs.length-1){//判断后一个字母的情况
            int temp = Integer.parseInt(""+chs[i]+chs[i+1]);
            if(temp>=10 && temp<=25){//可以连成字母
                if((i+2)<chs.length){
                    tempCount += res[i+2];//f(i+1)+f(i+2)
                }
                else{
                    tempCount += 1;
                }
            }
        }
        res[i] = tempCount;
    }
    return res[0];
}

62.礼物的最大价值

《剑指offer》p233、leetcode 64

题目:礼物的最大价值。

在一个m*n的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于0)。

从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。

例如,对于如下棋盘:

1*  10  3  8
12*  2  9  6
5*  *7  4  11
3  7*  16*  5*
	

res = 1+12+5+7+7+16+5 = 53

思路:

典型的动态规划。定义f(i,j)表示到达坐标为(i,j)的格子时能拿到的礼物总和的最大值(从左上到右下的顺序),最后求f(m-1,n-1)

递归分析:f(i,j) = max(f(i-1,j), f(i,j-1)) + gift[i,j]

循环计算:int[][] maxValue辅助,返回maxValue[m-1][n-1]

/**
 题目:礼物的最大价值。
 * 在一个m*n的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于0)。
 * 从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。
 * 例如,对于如下棋盘:
 *      1*    10   3    8
        12*   2    9    6
        5*    *7    4    11
        3    7*    16*   5*
    res = 1+12+5+7+7+16+5 = 53
 思路:
    典型的动态规划。定义f(i,j)表示到达坐标为(i,j)的格子时能拿到的礼物总和的最大值(从左上到右下的顺序),最后求f(m-1,n-1)
    递归分析:f(i,j) = max(f(i-1,j), f(i,j-1)) + gift[i,j]
    循环计算:int[][] maxValue辅助,返回maxValue[m-1][n-1]
 */
public class Solution{
    public static void main(String[] args){
        int[][] gift = {{1,10,3,8},{12,2,9,6},{5,7,4,11},{3,7,16,5}};
        System.out.println(maxValue(gift, 4, 4)); //out:53
    }
    public static int maxValue(int[][] gift, int m, int n){
        /**
         * @Description: m*n大小的棋盘gift[][]
         * 递归分析f(i,j) = max(f(i-1,j), f(i,j-1)) + gift[i,j]
         * 循环计算:int[][] maxValue辅助,返回maxValue[m-1][n-1]
         */
        if(gift==null || m<=0 || n<=0){
            return 0;
        }
        int[][] maxValue = new int[m][n];
        for(int i=0; i<m; ++i){ //从左上往右下进行循环计算
            for(int j=0; j<n; ++j){
                int temp = 0;
                if((i-1)>=0){
                    temp = Math.max(temp, maxValue[i-1][j]);
                }
                if((j-1)>=0){
                    temp = Math.max(temp, maxValue[i][j-1]);
                }
                maxValue[i][j] = temp + gift[i][j];
            }
        }
        return maxValue[m-1][n-1];
    }
}

优化改进空间复杂度,使用一维数组
•题目中可知,坐标(i,j)的最大礼物价值仅仅取决于坐标为(i-1,j)和(i,j-1)两个格子;
•因此第i-2行以上的最大价值没有必要保存下来。
•使用一维数组保存,数组的长度为gift的列数:(0…j-1)保存的是(i,0)…(i,j-1)的最大价值;(j…cols-1)保存的是(i-1,j)…(i-1,cols-1)的最大价值。即:数组前j个数字分别是当前第i行前j个格子的maxvule,而之后的数字分别保存前面第i-1行n-j个格子的maxVlue。

//leetcode 64 Minimum Path Sum的代码,一样的
public int minPathSum(int[][] grid) {
    if(grid==null || grid.length==0 || grid[0].length==0){
        return 0;
    }
    int m = grid.length;
    int n = grid[0].length;

    int[] min = new int[n];//辅助一维数组,数组的长度为grid的列数
    for(int i=0; i<m; ++i){
        for(int j=0; j<n; ++j){
            if(i == 0){//第一行
                if(j==0){//第一列
                    min[j] = grid[i][j];
                }
                else {
                    min[j] = grid[i][j] + min[j-1];
                }
            }
            else{//后面几行
                if(j==0){//第一列
                    min[j] = min[j] + grid[i][j];
                }
                else{
                    min[j] = Math.min(min[j-1], min[j]) + grid[i][j];
                }
            }
        }
    }
    return min[n-1];
}

63.最长不含重复字符的子字符串长度

《剑指offer》p236、leetcode 3

题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含从’a’到’z’的字符。例如,在字符串中”arabcacfr”,最长非重复子字符串为”acfr”,长度为4。

思路:

不重复子串可能有多个,而最长不重复子串长度只有一个唯一的值。

遍历一遍字符串,同时借助辅助hashMap记录字符是否出现及上一次出现的位置。时间o(n),空间o(n)

三个变量:

int resMax = 0; //记录最长的不重复字符子串的长度

int tempMax = 0; //记录当前不重复字符子串的长度

int startIndex = 0; //记录当前不重复字符子串的开始位置,初始化为0

//Longest Substring Without Repeating Characters最长不含重复字符的子字符串长度
//思路:遍历一遍字符串,辅助hashMap存字符是否出现过或上一次出现的位置。时间o(n), 空间o(n)
public int lengthOfLongestSubstring(String s) {
    if(s==null || s.length()==0){
        return 0;
    }
    if(s.length()==1){
        return 1;
    }


    int resMax = 0;//最终返回的最大长度
    int tempMax = 0;//当前的最大长度
    int startIndex = 0;//当前最大长度的起始位置

    //1.辅助hashMap存字符是否出现过或上一次出现的位置
    HashMap<Character, Integer> hashMap = new HashMap<>();

    //2.遍历一遍字符串
    for(int i=0; i<s.length(); ++i){
        char temp = s.charAt(i);
        //如果字符没出现过或字符上一次出现的位置在startIndex的前面,那么当前字符有效,计算更新相应长度即可
        if(!hashMap.containsKey(temp) || hashMap.get(temp)<startIndex){
            tempMax = i-startIndex+1;
            resMax = Math.max(tempMax, resMax);
        }else{//当前字符重复了
            startIndex = hashMap.get(temp)+1; //下次的startIndex为重复字符上一次出现位置的下一个位置
        }
        hashMap.put(temp, i);//不管重复与否,都要更新字符出现的位置
    }

    return resMax;
}

64.丑数(两道)

判断一个数是否为丑数--leetcode 263
求第N个丑数是几--《剑指offer》p240

1.判断一个数是否为丑数

leetcode 263

题目:给定一个数,判断是否为丑数(丑数定义:只含因子2、3、5的正数,1也算丑数)

Input: 6
Output: true
Explanation: 6 = 2 × 3
	

思路:就正常判断因子即可。

//判断一个数是否为丑数
public boolean isUgly(int num) {
    if(num<1) {
        return false;
    }
    if(num==1){
        return true;
    }
    while(num%2==0){
        num /= 2;
    }
    while(num%3==0){
        num /= 3;
    }
    while(num%5==0){
        num /= 5;
    }
    if(num==1){
        return true;
    }
    return false;
}

2.求第N个丑数是几

《剑指offer》p240

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

思路:

空间换时间。丑数一定是丑数*2/*3/*5得来的,直接用数组存下1到index的丑数

关键是怎么保证每一个丑数的大小顺序的–>三个变量:index2、index3、index5

每次该放下一个丑数时,三个变量分别记录了当前可以*2/*3/*5的最小的丑数位置

每次分别对这三个位置上的丑数23*5后找出最小的一个,即为当前的丑数,之后更新三个变量即可

//丑数
public static int GetUglyNumber_Solution(int index) {
    if(index<=0){
        return 0;
    }
    if(index==1){
        return 1;
    }
    int[] res = new int[index+1];//存1到index的丑数,返回res[index]
    res[1] = 1;
    int index2 = 1;
    int index3 = 1;
    int index5 = 1;
    for(int i=2; i<=index; ++i){
        res[i] = min(res[index2]*2, res[index3]*3, res[index5]*5);//该放res[i]丑数了,找三个中最小的
        while(res[index2]*2<=res[i]){//更新三个变量
            index2++;
        }
        while(res[index3]*3<=res[i]){
            index3++;
        }
        while(res[index5]*5<=res[i]){
            index5++;
        }
    }

    return res[index];
}

public static int min(int a, int b, int c){
    int temp = a<b ? a : b;
    return temp<c ? temp : c;
}

65.第一个只出现一次的字符

第一个只出现一次的字符(数组)--《剑指offer》p243、leetcode 387
第一个只出现一次的字符(字符流)--《剑指offer》p247

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

e.g.输入"abaccdeff" --> 则输出'b'

思路:不管是数组还是字符流,都用这个思路。遍历一遍str,辅助hashMap,value记录字符出现第一次的位置,若字符重复出现,则value记录-1。最后再遍历一遍hashMap,找除了-1之外的最小value即可所求。时间o(n),空间o(256)

数据是数组的代码:

//给定数组的代码,字符流的情况一样的。思路:遍历一遍str,辅助hashMap记录字符出现的位置,再遍历一遍hashMap即可。时间o(n),空间o(256)
public int firstUniqChar(String str) {
    HashMap<Character, Integer> hashMap = new HashMap<>();//key:字符,value:字符出现一次value为上次出现的位置,字符出现多次value存-1
    //1.遍历字符串,辅助hashMap记录字符出现的位置
    for(int i=0; i<str.length(); ++i){
        char temp = str.charAt(i);
        if(!hashMap.containsKey(temp)){//字符只出现一次,存出现的位置
            hashMap.put(temp,i);
        }
        else{//字符出现多次,存-1
            hashMap.put(temp, -1);
        }
    }

    //2.遍历hashMap来找第一次只出现一次的字符
    int res = Integer.MAX_VALUE;
    for(Map.Entry<Character, Integer> entry: hashMap.entrySet()){
        if(entry.getValue()!=-1){
            res = Math.min(res, entry.getValue());//找最小的只出现一次的字符位置
        }
    }
    return res==Integer.MAX_VALUE ? -1 : res;
}

数据是字符流的代码:

public class Solution{
	
    private int[] chIndex;
    private static int cnt = 0; //计数当前输入到第几个数了,初始为0
	
    public Solution(){
        chIndex = new int[256];//初始化哈希数组,每个字符都未出现,
    }
	
    //Insert one char from stringstream
    public void Insert(char ch){
        cnt++;
        if(chIndex[ch]==0) {//之前未出现过,则更新值为当前位置,说明出现了第一次
            chIndex[ch] = cnt;
        }
        else{ //之前出现过,因为要找的是只出现一次的,故之后这个字符已经无意义了,要找更新值为-2
            chIndex[ch] = -1;
        }
    }
	
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce(){
        int minIndex = Integer.MAX_VALUE; //记录最早出现一次字符的位置
        char resChar = '#'; //记录最早出现只出现一次的那个字符
        for(int i=0; i<256; ++i){//遍历哈希数组,找出只出现一次的字符(数组值>0)且数组值最小的(即为最早出现的)
            if(chIndex[i]>0 && chIndex[i]<minIndex){
                minIndex = chIndex[i];
                resChar = (char)i;
            }
        }
        return resChar;
    }
}

当然,本题中hashMap可以用256的数组,也可以用hashMap

private HashMap<Character, Integer> hashMap;
private static int cnt = 0; //计数当前输入到第几个数了,初始为0

public Solution(){
    hashMap = new HashMap<>();
}

//Insert one char from stringstream
public void Insert(char ch){
    cnt++;
    if(!hashMap.containsKey(ch)) {//之前未出现过,则更新值为当前位置,说明出现了第一次
        hashMap.put(ch, cnt);
    }
    else{ //之前出现过,因为要找的是只出现一次的,故之后这个字符已经无意义了,要找更新值为-2
        hashMap.put(ch, -1);
    }
}

//return the first appearence once char in current stringstream
public char FirstAppearingOnce(){
    int minIndex = Integer.MAX_VALUE; //记录最早出现一次字符的位置
    char resChar = '#'; //记录最早出现只出现一次的那个字符
    for(Map.Entry<Character, Integer> entry: hashMap.entrySet()){//遍历哈希数组,找出只出现一次的字符(数组值>0)且数组值最小的(即为最早出现的)
        if(entry.getValue()!=-1 && entry.getValue()<minIndex){
            minIndex = entry.getValue();
            resChar = entry.getKey();
        }
    }
    return resChar;
}

66.归并排序相关(3道)

数组的归并排序
数组中的逆序对个数(2道)--《剑指offer》p249、leetcode 493
单链表的归并排序--leetcode 148
	

1.数组的归并排序

经典的归并排序算法,时间o(nlog),空间o(n)

来源图解排序算法(四)之归并排序

import java.util.Arrays;
public class Solution {
    //归并排序。思路:分而治之,先分后治,辅助数组temp[]
    public static void mergeSort(int[] nums){
        if(nums==null || nums.length<2){
            return;
        }
        int[] temp = new int[nums.length]; //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
        mergeSortDetail(nums, 0, nums.length-1, temp);
    }
	
    //分
    private static void mergeSortDetail(int[] nums, int left, int right, int[] temp){
        if(left<right){//一个数字不用merge,只有两个以上才merge
            int mid = (left+right)/2;
            mergeSortDetail(nums, left, mid, temp);//左边归并排序,使得左子序列有序
            mergeSortDetail(nums, mid+1, right, temp);//右边归并排序,使得右子序列有序
            merge(nums, left, mid, right, temp);//将两个有序子数组合并操作
        }
    }
	
    //治
    private static void merge(int[] nums, int left, int mid, int right, int[] temp){
        int tempIndex = 0;//temp数组的当前位置
        int i = left; //左序列的索引
        int j = mid+1; //右序列的索引
        while(i<=mid && j<=right){//将左、右序列的元素有序的放进temp数组中
            temp[tempIndex++] = nums[i]<=nums[j] ? nums[i++] : nums[j++];
        }
        while(i<=mid){//将左边剩余元素填充进temp中
            temp[tempIndex++] = nums[i++];
        }
        while(j<=right){//将右边剩余元素填充进temp中
            temp[tempIndex++] = nums[j++];
        }
	
        //将temp中的元素全部拷贝到nums原数组中
        tempIndex = 0;
        while(left<=right){
            nums[left++] = temp[tempIndex++];
        }
    }
    public static void main(String []args){
        int[] nums = {9,8,7,6,5,4,3,2,1};
        mergeSort(nums);
        System.out.println(Arrays.toString(nums));
    }
}

2.数组中的逆序对(2道)

《剑指offer》p249
Reverse Pairs--leetcode 493 

2.1 求出数组中逆序对的个数

《剑指offer》p249

思路:用归并排序思想,在归并排序的过程中计数逆序对。时间o(nlogn),空间o(n),稳定。先求前面一半数组的逆序数,再求后面一半数组的逆序数,然后求前面一半数组比后面一半数组中大的数的个数(也就是逆序数),这三个过程加起来就是整体的逆序数目了。

注意:归并排序merge过程中的从右往左排序的巧妙思想

//数组中逆序对的总数。思路:归并排序思想
// 先求前面一半数组的逆序数,再求后面一半数组的逆序数,然后求前面一半数组比后面一半数组中大的数的个数
public static int InversePairs(int [] array) {
    if(array==null || array.length<2){
        return 0;
    }
    int[] temp = new int[array.length];
    return InversePairsDetail(array, 0, array.length-1, temp)%1000000007;
}
public static int InversePairsDetail(int[] array, int left, int right, int[] temp){
    int res = 0;
    if(left<right){
        int mid = (left+right)/2;
        res += InversePairsDetail(array, left, mid, temp);//左边的逆序数
        res += InversePairsDetail(array, mid+1, right, temp);//右边的逆序数
        res += merge(array, left, mid, right, temp);//左边与右边的逆序数
    }
    return res%1000000007;
}
public static int merge(int[] array, int left, int mid, int right, int[] temp){
    int res = 0;
    int i = mid;//左序列指针,从右往左
    int j = right;//右序列指针,从右往左
    int tempIndex = right;//临时数组指针,从右往左
    while(i>=left && j>mid){
        if(array[i]>array[j]){//是一个逆序对,且前面的比后面的大,又找到很多个逆序对
            temp[tempIndex--] = array[i--];
            res += (j-mid);//又找到很多个逆序对
            res %= 1000000007;
        }
        else{
            temp[tempIndex--] = array[j--];
        }
    }
    while(i>=left){//前面的数组还有
        temp[tempIndex--] = array[i--];
    }
    while(j>mid){//后面的数组还有
        temp[tempIndex--] = array[j--];
    }

    tempIndex = right; //最后还是要完成排序
    while(left<=right){
        array[right--] = temp[tempIndex--];
    }
    return res%1000000007;
}

2.2 Reverse Pairs

leetcode 493

题目:和上面的逆序数有点不一样,Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j].

Input: [1,3,2,3,1]
Output: 2
	

思路:还是逆序对的归并排序思想,只不过找逆序对的算法不同,因此简要修改判断即可

//数组中逆序对的总数。思路:归并排序思想
//先求前面一半数组的逆序数,再求后面一半数组的逆序数,然后求前面一半数组比后面一半数组中大的数的个数
public int reversePairs(int[] nums) {
    if(nums==null || nums.length<2){
        return 0;
    }
    int[] temp = new int[nums.length];
    return InversePairsDetail(nums, 0, nums.length-1, temp);
}
public int InversePairsDetail(int[] array, int left, int right, int[] temp){
    int res = 0;
    if(left<right){
        int mid = (left+right)/2;
        res += InversePairsDetail(array, left, mid, temp);//左边排序,且计算左边的逆序对个数
        res += InversePairsDetail(array, mid+1, right, temp);//右边排序,且计算右边的逆序对个数
        //merge左右两边排序前,计算左右两边的逆序对个数
        for (int i = left, j = mid+1; i <= mid && j <= right;){
            if (array[i] > (long) array[j] * 2){//long必须有
                res += mid - i + 1;
                j++;
            }
            else i++;
        }
        
        merge(array, left, mid, right, temp);//左右两边归并排序
    }
    return res;
}
//正常的左右两边归并排序
public void merge(int[] array, int left, int mid, int right, int[] temp){
    int i = mid;//左序列指针,从右往左
    int j = right;//右序列指针,从右往左
    int tempIndex = right;//临时数组指针,从右往左
    while(i>=left && j>mid){
        if(array[i]>array[j]){
            temp[tempIndex--] = array[i--];
        }
        else{
            temp[tempIndex--] = array[j--];
        }
    }
    while(i>=left){//前面的数组还有
        temp[tempIndex--] = array[i--];
    }
    while(j>mid){//后面的数组还有
        temp[tempIndex--] = array[j--];
    }

    tempIndex = right; //最后还是要完成排序
    while(left<=right){
        array[right--] = temp[tempIndex--];
    }
}

3.单链表的归并排序

leetcode 148

93.0单链表的归并排序

67.二叉搜索树的第k小节点(中序遍历)

《剑指offer》p269、leetcode 230

题目:给定一棵二叉搜索树,请找出其中的第k小的结点。如(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。

思路:中序遍历即可。时间o(n),空间o(1)

//二叉搜索树中第k小节点。将k存到数组中便于修改传参,对二叉树中序遍历判断即可 
TreeNode KthNode(TreeNode pRoot, int k)
{
    if(pRoot==null || k<1){
        return null;
    }
    int[] ks = new int[1];
    ks[0] = k;
    return KthNodeDetail(pRoot, ks);
}
TreeNode KthNodeDetail(TreeNode pRoot, int[] ks)
{
    TreeNode res = null;
    if(pRoot.left!=null){
        res = KthNodeDetail(pRoot.left, ks);
    }
    if(res!=null){
        return res;
    }
    ks[0]--;
    if(ks[0]==0){
        return pRoot;
    }
    if(pRoot.right!=null){
        res = KthNodeDetail(pRoot.right, ks);
    }
    return res;
}

68.二叉树的深度(两道)

	求二叉树的深度--leetcode104
	判断是否为平衡二叉树--leetcode110
	

1.求二叉树的深度

leetcode104、《剑指offer》p271

思路:递归

public int treeDepth(TreeNode head){
    if(head==null){
        return 0;
    }
    return Math.max( treeDepth(head.left)+1, treeDepth(head.right)+1 );
}

2.判断是否为平衡二叉树

leetcode110、《剑指offer》p271

题目:给一个二叉树,判断是否为平衡二叉树

思路1:遍历树,对每一个节点进行判断是否满足平衡树的条件—>递归重复判断计算,不可取

//判断是否为平衡二叉树
public boolean IsBalanced_Solution(TreeNode root) {
    if(root==null){
        return true;
    }
    int left = getDepth(root.left);
    int right = getDepth(root.right);
    if(Math.abs(left-right)<2){
        return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }
    return false;
}
//求二叉树的深度
public int getDepth(TreeNode root){
    if(root==null){
        return 0;
    }
    return 1 + Math.max(getDepth(root.left), getDepth(root.right));
}

思路2:后序遍历(左右根),从下往上依次判断每个节点是否满足平衡二叉树的条件。这样每个节点的高度只会算一次,避免自上而下递归判断的重复计算—>可取

//判断是否为平衡二叉树。好的方法:后序遍历(左右根),从下往上依次判断每个节点是否满足平衡二叉树的条件。
// 这样每个节点的高度只会算一次,避免自上而下递归判断的重复计算
public boolean IsBalanced_Solution(TreeNode root) {
    if(root==null){
        return true;
    }
    if(IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right)){
        int left = getDepth(root.left);
        int right = getDepth(root.right);
        if(Math.abs(left-right)<2){
            return true;
        }
    }
    return false;
}
//求二叉树的深度
public int getDepth(TreeNode root){
    if(root==null){
        return 0;
    }
    return 1 + Math.max(getDepth(root.left), getDepth(root.right));
}

69.数组中和为s的数字(四道)

数组中和为s的两个数字(有序数组)--《剑指offer》p280
数组中和为s的两个数字(无序数组)--leetcode 1
打印出和为s的所有连续正数序列--《剑指offer》p280
求和为s的所有连续正数序列的总数(转化为找因子的思想)--leetcode 829
	

1.数组中和为s的两个数字(有序数组)

《剑指offer》p280

题目:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

思路:从数组两端向中间扫,时间o(n),空间o(1),当然因为有序可以加一点二分的思想减少比较次数。

public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
    ArrayList<Integer> res = new ArrayList<>();
    int resMax = Integer.MAX_VALUE;

    int left = 0;
    int right = array.length-1;
    //left、right往中间扫
    while(left<right){
        int mid = left + (right-left)/2;
        if(array[mid]>=sum){//二分判断,需要往左边找
            right = mid - 1;
            continue;
        }
        int temp = array[left] + array[right];
        if(temp==sum){
            if(array[left]*array[right]<resMax){//看是否乘积最小
                resMax = array[left]*array[right];
                if(!res.isEmpty()){
                    res.clear();
                }
                res.add(array[left]);
                res.add(array[right]);
            }
        }
        if(temp < sum){//左边的数++
            left++;
        }
        else{//右边的数--
            right--;
        }
    }

    return res;
}

2.数组中和为s的两个数字(无序数组)

leetcode 1

题目:Given an array of integers(无序), return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

思路:因为数组无序,因此需要辅助map,时间o(n),空间o(n)

public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for(int i=0; i<nums.length; ++i){
        int temp = target - nums[i];
        if(map.containsKey(temp)){
           return new int[]{map.get(temp), i};
        }
        map.put(nums[i], i);
    }
    return new int[]{0,0};
}

3.打印出和为s的所有连续正数序列

《剑指offer》p280、leetcode 829

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

如:输入15,1+2+3+4+5=4+5+6=7+8=15,因此输出{1,2,3,4,5},{4,5,6},{7,8}三个序列

思路:

思路:

	从递增数组中两个和为s的数得到启示,设置两个变量,一个记录当前序列的最小的数small,一个记录当前序列的最大的数big。
	初始化small=0,big=1
	若是当前的正数序列之和小于S,big++
	若是当前的正数序列之和大于S,small++
	因为和为s的序列至少包括两个数,所以small要小于等于s的一半
	

代码:

//和为s的连续整数序列
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    if(sum<3){
        return res;
    }
    int small = 1;
    int big = 2;
    int tempRes = small+big;//当前序列的和
    ArrayList<Integer> tempList = new ArrayList<>();//缓存当前序列
    tempList.add(small);
    tempList.add(big);
    while(small <= sum/2){//至少两个数的和为sum,所以small不能超过sum的一半
        if(tempRes<=sum){
            if(tempRes==sum){
                res.add(new ArrayList<>(tempList));
            }
            big++;
            tempRes += big;
            tempList.add(big);
        }
        else if(tempRes>sum){
            tempRes-=small;
            tempList.remove((Object)small);
            small++;
        }
    }
    return res;
}

4.求和为s的所有连续正数序列的总数(转化为找因子的思想)

leetcode 829

题目:Given a positive integer N, how many ways can we write it as a sum of consecutive positive integers? 和为s的连续整数序列一共有多少种。和为s的连续整数序列一共有多少种,题目中N特别大,o(n)会超时,因此必须o(logn)。

	Input: 5
	Output: 2
	Explanation: 5 = 5 = 2 + 3
	

思路:变型求和公式转化问题,时间o(logn),空间o(1)。

详细思路: 连续数列的求和公式–>(n1+n2)*(n2-n1+1)=2*N,(n1<n2),其中(n1+n2)和(n2-n1+1)一定要是2*N的因子,因此转化成求2*N的因子的问题,将时间复杂度控制在了O(logn)

public int consecutiveNumbersSum(int N) {
    int res = 1;//一共有res种方法,本身也算一种,因此初始为1
    int target = 2*N;
    int index = (int)Math.sqrt(target);

    for(int i=2; i<=index; ++i){//从2遍历到index找target的因子,直接优化到了o(logn)
        if(target%i==0){//i是target的一个因子
            //两元一次方程解出n1、n2,注意n1<n2
            int n2 = (i+target/i-1)/2;
            int n1 = target/i-n2;
            if(n1<n2 && (n1 + n2)*(n2 - n1 + 1)==target){//找到一个从n1~n2的连续序列和为s
                res++;
            }
        }
    }
    return res;
}

70.翻转字符串(两道)

	给定一句话,翻转这句话的单词顺序(单词不变)--《剑指offer》p284、leetcode 151
	左旋转字符串(abcXYZdef循环左移3位-->XYZdefabc)--《剑指offer》p284
	

1.翻转一句话的单词顺序(单词不变)

《剑指offer》p284、leetcode 151

题目:给定一句话,翻转这句话的单词顺序(单词不变)

思路:先split(" ")成数组,再反着拼装即可。时间o(n),空间o(n)

public String reverseWords(String s) {
    String[] strs = s.split(" ");
    StringBuilder res = new StringBuilder();
    for(int i=strs.length-1; i>=0; --i){
        if(strs[i].equals("")){
            continue;
        }
        res.append(strs[i] + " ");
    }
    return res.toString().trim();
}

2.左旋转字符串

《剑指offer》p284

题目:左旋转字符串(abcXYZdef循环左移3位–>XYZdefabc)

思路1(不可取): 用substring()函数拼接即可,当然substring()会依赖辅助空间。

//循环左移字符串,abcXYZdef左移3位-->XYZdefabc
public static String LeftRotateString(String str,int n) {
    if(n<1 || str==null || str.length()<2){
        return str;
    }
    return str.substring(n) + str.substring(0,n);
}

思路2(可取): 当不能用substring()去依赖辅助空间时,直接在原字符串上修改。分成需要移位的左边和剩下右边的两部分,先将这两部分分别翻转,然后再整个翻转即可。时间o(n),空间o(1)

//循环左移字符串,abcXYZdef左移3位-->XYZdefabc
public static String LeftRotateString(String str,int n) {
    if(n<1 || str==null || str.length()<2){
        return str;
    }
    char[] chs = str.toCharArray();
    reverse(chs, 0, n-1);
    reverse(chs, n, chs.length-1);
    reverse(chs,0,chs.length-1);
    return String.valueOf(chs);
}
//翻转一个字符串:首尾向中间扫,字符互换即可
private static void reverse(char[] str, int start, int end){
    if(start>=end){
        return;
    }
    while(start<end){
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        start++;
        end--;
    }
}

71.滑动窗口最大值(双端队列)

《剑指offer》p288、《左神》19、leetcode239

题目:滑动窗口的最大值。给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。如{2,3,4,2,6,2,5,1}及滑动窗口的大小3,最大值分别为{4,4,6,6,6,5}

思路:双端队列。滑动窗口先进先出因此借助双端队列,使用队列保存数组的下标,从头遍历数组,根据如下规则进行入队、出队:

0. 如果队列为空,则当前数字入队列
1. 如果当前数字大于队列尾,则删除队列尾,然后当前数字入队列
2. 如果当前数字小于等于队列尾,则当前数字入队列
3. 如果队列头超出滑动窗口范围,则删除队列头
4. 这样能始终保证队头为当前的最大值
5. 时间o(n),空间o(k)

代码:

//滑动窗口最大值-->双端队列
public static int[] maxSlidingWindow(int[] nums, int k) {
    if(nums==null || nums.length==0 || k<1 || k>nums.length){
        return new int[0];
    }

    int[] res = new int[nums.length-k+1];//滑动窗口最大值
    int resCnt = 0;
    Deque<Integer> deque = new LinkedList<>();//双端队列,存的不是数组元素,而是下标,便于判断滑动窗口位置
    for (int i=0; i<nums.length; ++i){//遍历数组
        if(!deque.isEmpty() && deque.getFirst()+k==i){//进行滑动窗口:双端队列的头部已经不在窗口中,头部出队
            deque.pollFirst();
        }
        while(!deque.isEmpty() && nums[i]>nums[deque.getLast()]){//如果当前数比双端队列的尾部大,则一直pollLast,直到当前数比尾部小或相等
            deque.pollLast();
        }
        deque.offerLast(i);

        if(i+1>=k){//存储当前窗口最大值
            res[resCnt++] = nums[deque.getFirst()];
        }
    }
    return res;
}

72.打印n个骰子所有可能的点数和及概率

《剑指offer》p294.

题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s,输入n,打印出s的所有可能的值出现的概率。示例如下(n=2):

2个骰子,点数和为2出现的概率为: 0.027777777777777776
2个骰子,点数和为3出现的概率为: 0.05555555555555555
2个骰子,点数和为4出现的概率为: 0.08333333333333333
2个骰子,点数和为5出现的概率为: 0.1111111111111111...

递归思路:

def getsumcount(self, number, summ):
    if number < 1 or summ < number or summ > 6 * number:
        return 0
    if number == 1:
        return 1
    resCount = 0
    resCount = self.getsumcount(number-1, summ-1)+self.getsumcount(number-1, summ-2)+self.getsumcount(number-1, summ-3)+self.getsumcount(number-1, summ-4)+self.getsumcount(number-1, summ-5)+self.getsumcount(number-1, summ-6)
    return resCount
def po(self, number):
    total = pow(6, number)
    for i in range(number, 6*number+1):
        res = self.getsumcount(number, i)
        ratio = res/total
        print(i, ratio)
        

循环思路:先算所有出现的点数和及出现次数,再算概率(出现次数/6^number)。一个一个骰子往上加,辅助两个数组交替着存所有出现的点数和为n的出现次数,resTemp[n]=res[n-1]+res[n-2]+res[n-3]+res[n-4]+res[n-5]+res[6]

详细思路(可以不看了,初学看):n个骰子的总点数,最小为n,最大为6n,n个骰子出现的所有点数排列的个数为6n。我们先统计每一个点数和出现的次数,定义一个长度为6n-n+1的数组,和为s的点数出现的次数保存到数组的第s-n个元素里;然后把每一个点数和出现的次数除以6n,就能求出每个点数和出现的概率。因此本题重点是统计每一个点数和出现的次数,不难发现这是一种递归的思路,自下而上循环实现,从1个骰子开始,每次加一个骰子计算。用两个数组来存储骰子点数和的每一种出现的次数。在一次循环中,第一个数组中的第n个数字表示骰子和为n出现的次数。在下一次循环中我们加上一个新的骰子,此时和为n的骰子出现的次数应该等于上一次循环中骰子点数和为n-1、n-2、n-3、n-4、n-5与n-6的次数的综合,所以我们把另一个数组的第n个数字设为前一个数组对应的第n-1、n-2、n-3、n-4、n-5与n-6之和。

代码:

//n个骰子的所有出现的点数和及概率。思路:两个数组存所有出现的点数和及出现次数,再算概率。
//详细思路:一个一个骰子往上加,点数和为n的出现次数resTemp[n]=res[n-1]+res[n-2]+res[n-3]+res[n-4]+res[n-5]+res[6]
public static void printSumRatio(int number){
    if(number<1){
        return;
    }
    //1.辅助两个数组并初始化
    int[][] res = new int[2][6*number+1];//定义两个数组,交换着统计number个骰子所有可能的点数和出现的次数,数组长度为6*n+1
    int resFlag = 0;//交换数组的标志位

    for(int i=1; i<=6; ++i){//当1个骰子时,初始化res数组
        res[resFlag][i] = 1;
    }

    //2.交换着使用两个数组,统计number个骰子所有点数和出现的次数
    for(int i=2; i<=number; ++i){//一个一个骰子往上加
        for(int j=1; j<i; ++j){//当有i个骰子时,[1,i)的点数和为0,全部清空
            res[1-resFlag][j] = 0;
        }
        for(int j=i; j<=6*i; ++j){//当有i个骰子时,给[i,6*i]赋值
            for(int k=1; k<=6&&j>k; ++k){
                res[1-resFlag][j] += res[resFlag][j-k];//resTemp[n]=res[n-1]+res[n-2]+res[n-3]+res[n-4]+res[n-5]+res[6]
            }
        }

        resFlag = 1-resFlag;//交换数组,改变flag
    }

    //3.计算并打印number个骰子所有可能的点数和及概率
    double totalCount = Math.pow(6, number);//number个骰子出现的不同情况的总次数为6^n次方
    for(int i=number; i<=6*number; ++i){
        double ratio = (double)res[resFlag][i]/totalCount;//点数和为res[i]的概率
        System.out.println(number + "个骰子,点数和为" + i + "出现的概率为" + ratio);
    }
}

public static void main(String[] args){
    printSumRatio(2);
    //out:
    //2个骰子,点数和为2出现的概率为0.027777777777777776
    //2个骰子,点数和为3出现的概率为0.05555555555555555
    //2个骰子,点数和为4出现的概率为0.08333333333333333
    //2个骰子,点数和为5出现的概率为0.1111111111111111
    //2个骰子,点数和为6出现的概率为0.1388888888888889
    //2个骰子,点数和为7出现的概率为0.16666666666666666
    //2个骰子,点数和为8出现的概率为0.1388888888888889
    //2个骰子,点数和为9出现的概率为0.1111111111111111
    //2个骰子,点数和为10出现的概率为0.08333333333333333
    //2个骰子,点数和为11出现的概率为0.05555555555555555
    //2个骰子,点数和为12出现的概率为0.027777777777777776
}

73.扑克牌中的顺子(2道)

整个数组是否为一个顺子(同时有大小王)--《剑指offer》p298.
整个数组分组后每组都要为顺子--leetcode 846

1.整个数组是否为一个顺子(同时有大小王)

《剑指offer》p298.

题目:扑克牌中的顺子:从扑克牌中随机抽5张牌,判断是不是顺子(即这5张牌是不是连续的)。2-10为数字本身,A为1,J、Q、K 为11、12、13,大小王可以看成任意的数字(解题时可以看成0)。

思路:

0.定义长度为numbers的数组(这里不限于解决5张,判断numbers张牌是否为顺子);
1.数组排序;
2.统计数组中0的个数;
3.统计排序后数组相邻数字间的空缺数(eg数字57间空缺1);
4.2、3两步统计的0个数大于等于空缺数-->顺子,否则不是顺子

代码:

//判断扑克牌顺子(2张及以上连续),大小王0可以代表任意牌。思路:数组排序,记录大小王的个数,遍历数组判断是否为顺子即可
public static boolean isContinuous(int [] numbers) {
    if(numbers==null || numbers.length>13 || numbers.length<2){
        return false;
    }
    //1.数组排序
    Arrays.sort(numbers);

    //2.记录大小王的个数并判断是否为顺子
    int superCnt = 0;//记录大小王的个数
    int superCntCopy = 0;//保存大小王个数
    for(int i=0; i<numbers.length-1; ++i){
        if(numbers[i]==0){//是大小王
            superCnt++;
            superCntCopy++;
        }
        else if(numbers[i]==numbers[i+1]){//相等,即出现对儿,不是顺子
            return false;
        }
        else if(numbers[i]+1==numbers[i+1]){//连续
            continue;
        }
        else if(numbers[i]+1!=numbers[i+1]){//遇到不连续的牌,看看能不能凑个王
            if( (numbers[i+1]-numbers[i]-1) <= superCnt ){//可以用王凑
                superCnt -= (numbers[i+1]-numbers[i]-1);
            }else{//不够王了
                return false;
            }
        }
    }
    //3.王太多还剩下几张王,放在牌头与牌尾后还剩下王,则不是顺子
    if(superCnt>0 && superCnt>(numbers[superCntCopy]-numbers[numbers.length-1]+12)){
        return false;
    }
    return true;
}

2.整个数组分组后每组都要为顺子

leetcode 846

题目:Alice has a hand of cards, given as an array of integers. Now she wants to rearrange the cards into groups so that each group is size W, and consists of W consecutive cards. Return true if and only if she can.

Input: hand = [1,2,3,6,2,3,4,7,8], W = 3
Output: true
Explanation: Alice's hand can be rearranged as [1,2,3],[2,3,4],[6,7,8].

思路:1.hand长度不能整除W,false;2.hand排序;3.由于[1,2,3],[2,3,4]也可以,需要辅助数组缓存访问标记用于后续判断;4.遍历数组一组一组进行判断

public boolean isNStraightHand(int[] hand, int W) {
    //1.hand长度不能整除W,false
    if(hand==null || hand.length==0 || W<1 || W>hand.length || hand.length%W!=0){
        return false;
    }

    //2.hand排序
    Arrays.sort(hand);

    //3.辅助数组缓存访问标记用于后续判断
    boolean[] visited = new boolean[hand.length];

    //4.遍历数组一组一组进行判断
    int cnt = W;
    for(int i=0; i<hand.length; ++i){
        if(!visited[i]){//如果没有被访问过之前,以此为新group的起始位置开始寻找一个group
            visited[i] = true;
            int left = i;
            int right = i+1;
            while(cnt!=1){//寻找一个group
                if(right>=hand.length){//找完了hand都没凑成一个group,false
                    return false;
                }
                if(!visited[right]){
                    if(hand[left]+1<hand[right]){//断层太大,不可能连续了,false
                        return false;
                    }
                    else if(hand[left]+1==hand[right]){//前后连续
                        visited[right] = true;
                        left = right;
                        cnt--;
                    }
                }
                right++;
            }
            //找完了一个group,重置cnt
            cnt = W;
        }
    }
    return true;//找完了所有group,没有返回false说明都符合条件,true
}

74.圆圈中剩下的数(约瑟夫环问题)

《剑指offer》p300、《左神》43、leetcode 292

题目:圆圈中最后剩下的数字。0,1…n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈中删除第m个数字,求出这个圆圈里剩下的最后一个数字。e.g n=5([0,1,2,3,4]),m=3,out:3

思路1:用双端队列模拟这个循环环的问题,每排除一个数需要m步运算,n个数就需要O(mn),空间复杂度为O(n)

//圆圈中剩下的数。思路:用双端队列模拟这个过程,时间o(nm),空间o(n)
public static int LastRemaining_Solution(int n, int m) {
    if(m<1 || n<1){
        return -1;
    }
    if(n==1){
        return 1;
    }

    //1.辅助o(n)双端队列
    Deque<Integer> deque = new LinkedList<>();
    for(int i=0; i<n; ++i){
        deque.offerLast(i);
    }

    //2.模拟弹出过程,直到双端队列中剩下一个数为止
    int cnt = m;
    while(deque.size()>1){
        cnt--;
        if(cnt==0){//弹出一个数,重置cnt
            deque.pollFirst();
            cnt = m;
        }
        else{
            deque.offerLast(deque.pollFirst());
        }
    }
    return deque.pollFirst();
}

思路2(更好):分析这个题的数学规律,直接求解:f(n,m)=[(f(n-1,m)+m]%n,时间o(n),空间o(1)。

//f(n,m)=[(f(n-1,m)+m]%n
//f(n,m)=0(n=1);f(n,m)=[(f(n-1,m)+m)%n](n>1)
public int LastRemaining_Solution(int n, int m) {
    if(m<1 || n<1){
        return -1;
    }
    int res = 0;
    for(int i=2; i<=n; ++i){
        res = (res+m)%i;
    }
    return res;
}

75.股票的最大利润问题(四道)

只允许买卖一次股票,求最大收益--《剑指offer》p304、leetcode 121
允许多次买卖,求最大收益--《剑指offer》p304、leetcode 122
只允许买卖两次股票,求最大收益--leetcode 123
只允许买卖k次股票,求最大收益--leetcode 188

1.只允许买卖一次股票,求最大收益

《剑指offer》p304

题目:只允许买卖一次股票,求最大收益

思路:以当前股票为基准,找到之前的最小值,用来更新最大利润。时间o(n)

//股票的最大利润,只允许买卖一次股票,求最大收益。思路:两个变量即可,每天都尝试卖一次,不断更新最大利润。时间o(n),空间o(1)
public int maxProfit(int[] prices) {
    if(prices==null || prices.length<2){
        return 0;
    }
    int min = prices[0];//存之前最小的股价
    int resMax = 0; //存最大的利润
    for(int i=1; i<prices.length; ++i){//遍历股价,每天都尝试卖一次,不断更新最大利润
        resMax = Math.max(resMax, prices[i]-min);
        if(prices[i]<min){
            min = prices[i];
        }
    }
    return resMax;
}

2.允许多次买卖,求最大收益

《剑指offer》p304、leetcode 122

题目:允许多次买卖,求最大收益

思路:每次都头一天买,第二天卖出,把所有利润为正的加起来即为最大利润。时间o(n),空间o(1)

//股票的最大利润,允许多次买卖,求最大收益。思路:每次都头一天买,第二天卖出,把所有利润为正的加起来即为最大利润。时间o(n),空间o(1)
public int maxProfit(int[] prices) {
    if(prices==null || prices.length<2){
        return 0;
    }
    int resMax = 0; //存最大的利润
    for(int i=1; i<prices.length; ++i){//遍历股价,每天都尝试卖一次,不断更新最大利润
        int temp = prices[i]-prices[i-1];
        if(temp>0){
            resMax += temp;
        }
    }
    return resMax;
}

3.只允许买卖两次股票,求最大收益

leetcode 123

题目:只允许买卖两次股票,求最大收益

思路:3、4两题可以用DP解决,但是这里用更巧妙的方法,直接看第4题即可,本题的解法就是第4题的k=2

4.只允许买卖k次股票,求最大收益

leetcode 188

题目:只允许买卖k次股票,求最大收益

思路:3、4两题都用二维dp。dp[i][j]表示截止第j天进行i次交易的最大收益。

//k次买卖的最大股票收益。思路:二维dp。dp[i][j]表示截止第j天进行i次交易的最大收益
public int maxProfit(int k, int[] prices) {
    int len = prices.length;
    if (k >= len / 2) return quickSolve(prices);//可以最多次买卖,求最大收益即可

    int[][] t = new int[k + 1][len];
    for (int i = 1; i <= k; i++) {
        int tmpMax =  -prices[0];//tmpMax就是自己的钱包,就是当前的收益
        for (int j = 1; j < len; j++) {
            t[i][j] = Math.max(t[i][j - 1], prices[j] + tmpMax);//卖
            tmpMax =  Math.max(tmpMax, t[i - 1][j - 1] - prices[j]);//买
        }
    }
    return t[k][len - 1];
}

//允许多次买卖,把所有的收益都加起来即可
private int quickSolve(int[] prices) {
    int len = prices.length, profit = 0;
    for (int i = 1; i < len; i++)
        // as long as there is a price gap, we gain a profit.
        if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
    return profit;
}

76.求1+2+3+…+n

《剑指offer》p307.

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

思路:递归。

这里可以通过递归的方式进行计算,但是很疑问的地方在于如何结束递归

这里可以有多种的方式,基本方向是采用逻辑与或的方式来计算,

用或运算通过n==0来短路,这样在n=0的时候不需要计算递归的值

public static int Sum_Solution(int n) {
    int res = 0;
    boolean b = n==0 || (res=n+Sum_Solution(n-1))>0;
    return res;
}

77.构建乘积数组

《剑指offer》p312、leetcode 238

题目:给定一个数组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]。不能使用除法。

思路1(不可取):如果没有不能使用除法的限制,可以用公式B[i]=A[0]*A[1]*…*A[n-1]/A[i]表示,使用除法时要特别注意A[i]等于0的情况。时间o(n),空间o(1),但是这里不能用除法

思路2(不可取):现在要求不能使用除法,一个直观的解法是用连乘n-1个数字得到B[i],时间o(n^2)

思路3(可取):辅助一个数组temp[]的思想(但这里就用B做辅助数组),时间o(n),空间o(1)

1.先从后往前存储(temp[i]=A[i]*A[i+1]*…A[length-1]),此时先将B[]看成辅助数组temp[]

2.再从前往后计算(tempRes=A[0],B[0]=tempRes*temp[i-1],B[1]=tempRes*A[1]*temp[i]…)

代码:

public int[] multiply(int[] A) {
    if(A==null || A.length==0){
        return new int[0];
    }
    int[] B = new int[A.length];//存放结果,但从后往前计算时先将B看成辅助数组

    //1.1.先从后往前存储(temp[i]=A[i]*A[i+1]*...A[length-1]),此时先将B[]看成辅助数组
    B[A.length-1] = A[A.length-1];
    for(int i=A.length-2; i>=0; i--){
        B[i] = B[i+1] * A[i];
    }

    //2.再从前往后计算(tempRes=1,B[0]=tempRes*temp[i-1],B[1]=tempRes*A[1]*temp[i]...)
    int tempRes = 1;
    for(int i=0; i<A.length-1; ++i){
        B[i] = tempRes * B[i+1];
        tempRes *= A[i];
    }

    B[A.length-1] = tempRes;//最后一个元素

    return B;
}

78.普通二叉树中两个节点的最低公共祖先

《剑指offer》p326、leetcode 236

题目:普通二叉树中两个节点的最低公共祖先

思路1(不可取):从root开始寻找

如果当前节点的左子节点是两个节点的祖先(应该继续对左子节点判断)

如果当前节点的右子节点是两个节点的祖先(应该继续对右子节点判断)

当前结点不同时为两个结点的祖先(一边一个),即为最低公共祖先

这种方法自上而下重复遍历,不太好

class TreeNode {
    int val;
    TreeNode left = null;
    TreeNode right = null;
    TreeNode(int val) {
        this.val = val;
    }
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (hasNode(root.left, p) && hasNode(root.left, q)) {
        return lowestCommonAncestor(root.left, p, q);
    } else if (hasNode(root.right, p) && hasNode(root.right, q)) {
        return lowestCommonAncestor(root.right, p, q);
    }
    return root;
}
	
//遍历以root为根的树,判断是否含有节点p
public boolean hasNode(TreeNode root, TreeNode p) {
    if (root == null) {
        return false;
    }
    if (root == p) {
        return true;
    }
    return hasNode(root.left, p) || hasNode(root.right, p);
}

思路2(可取):遍历两次树,分别找出从root到两个节点的路径(用两个List存下来),变成求两个list中的最后一个公共结点

//普通二叉树中两个节点的最低公共祖先
public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q){
    if(root==null || p==null || q==null){
        return null;
    }
    ArrayList<TreeNode> arrayListP = new ArrayList<>();
    ArrayList<TreeNode> arrayListQ = new ArrayList<>();//两个List存路径
    if(!findPath(root,p,arrayListP) || !findPath(root,q,arrayListQ)){//没找到p、q
        return null;
    }

    int index = 0;
    while(index<arrayListP.size() && index<arrayListQ.size()){//对两个路径list找最后一个公共节点
        if(arrayListP.get(index)!=arrayListQ.get(index)){
            break;
        }
        index++;
    }
    return index==0 ? root: arrayListP.get(index-1);
}
//遍历二叉树,找root到target节点的路径(路径存在res中),如果没找到target返回true
public boolean findPath(TreeNode root, TreeNode target, ArrayList<TreeNode> res){
    if(root==null){
        return false;
    }
    res.add(root);
    if(root==target){
        return true;
    }
    if (findPath(root.left, target, res) || findPath(root.right, target, res)){
        return true;
    }
    res.remove(res.size()-1);//回溯
    return false;
}

79.把字符串转换成整数

《剑指offer》p318、leetcode 8

题目:把字符串(包括数字字母符号,可以为空)转换成整数,不符合条件返回0。
即实现Integer.valueOf(string)的功能,要求不能使用字符串转换整数的库函数

思路:字符串转换成数组,res用long,从前往后遍历即可,本题核心是对以下情况的处理

要点:

1.字母判断
2.溢出
3.正负号

代码:

public int myAtoi(String str){
    if(str==null || str.trim().length()==0){
        return 0;
    }

    char[] chs = str.trim().toCharArray();//转换成数组
    boolean isPositive = true;//正负号
    int index = 0;
    long res = 0;

    if(chs[index]=='+' || chs[index]=='-'){
        isPositive = chs[index]=='-' ? false : true;
        index++;
    }

    while(index<chs.length){
        int temp = chs[index]-'0';
        if(temp<0||temp>9){//是否是数字判断
            return 0;//这里在leetoce中应该为break,题目要求有一点点不一样
        }
        res = res*10L + (long)temp;
        index++;
        if(isPositive==true){//溢出判断
            if(res>Integer.MAX_VALUE){
                return Integer.MAX_VALUE;
            }
        }
        else{
            if(res*(-1L)<Integer.MIN_VALUE){
                return Integer.MIN_VALUE;
            }
        }
    }

    return isPositive==true ? (int)res : (int)res*(-1);//正负号判断
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值