当前位置 博文首页 > xiaochengzi0419的博客:剑指offer系列刷题笔记

    xiaochengzi0419的博客:剑指offer系列刷题笔记

    作者:[db:作者] 时间:2021-09-04 15:29

    剑指offer应该属于比较基础且高频的算法知识点,应该融合贯通,学习里面优秀的算法思维
    当然在解题时,我们尽量使用时间复杂度和空间复杂度比较好的算法
    一共75道题:主要分为解题思路和题解

    01 数组和字符串

    剑指 Offer 04. 二维数组中的查找

    在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
    请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
    
     
    
    示例:
    
    现有矩阵 matrix 如下:
    
    [
      [1,   4,  7, 11, 15],
      [2,   5,  8, 12, 19],
      [3,   6,  9, 16, 22],
      [10, 13, 14, 17, 24],
      [18, 21, 23, 26, 30]
    ]
    
    给定 target = 5,返回 true。
    
    给定 target = 20,返回 false。
    

    解题思路

    题解

    class Solution {
        public boolean findNumberIn2DArray(int[][] matrix, int target) {
            if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
                return false;
            }
            int rows = matrix.length, columns = matrix[0].length;
            int row = 0, column = columns - 1;
            while (row < rows && column >= 0) {
                int num = matrix[row][column];
                if (num == target) {
                    return true;
                } else if (num > target) {
                    column--;
                } else {
                    row++;
                }
            }
            return false;
    }
    

    剑指 Offer 05. 替换空格

    请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
    示例 1:
    
    输入:s = "We are happy."
    输出:"We%20are%20happy."
    

    解题思路:
    标签:字符串
    最简单的方案自然是直接使用库函数啦!当然题目肯定是不希望我们这样做的!
    增加一个新字符串,遍历原来的字符串,遍历过程中,如果非空格则将原来的字符直接拼接到新字符串中,如果遇到空格则将%20拼接到新字符串中
    时间复杂度:O(n)O(n),空间复杂度:O(n)O(n)

    class Solution {
        public String replaceSpace(String s) {
            StringBuilder sb = new StringBuilder();
            for(int i = 0 ; i < s.length(); i++){
                char ch = s.charAt(i);
                if(ch == ' ') {
                    sb.append("%20");
                }
                else {
                    sb.append(ch);
                }
            }
            return sb.toString();    // 这个转化非常重要
        }
    }
    

    剑指 Offer 11. 旋转数组的最小数字

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

    示例 1:
    
    输入:[3,4,5,1,2]
    输出:1
    示例 2:
    
    输入:[2,2,2,0,1]
    输出:0
    

    解题方案:
    思路:
    标签:二分查找
    整体思路:首先数组是一个有序数组的旋转,从这个条件可以看出,数组是有大小规律的,可以使用二分查找利用存在的规律快速找出结果
    时间复杂度:O(logn)O(logn),空间复杂度:O(1)O(1)
    算法流程
    初始化下标 left 和 right
    每次计算中间下标 mid = (right + left) / 2?,这里的除法是取整运算,不能出现小数
    当 numbers[mid] < numbers[right]? 时,说明最小值在 ?[left, mid]? 区间中,则令 right = mid,用于下一轮计算
    当 numbers[mid] > numbers[right]? 时,说明最小值在 [mid, right]? 区间中,则令 left = mid + 1,用于下一轮计算
    当 numbers[mid] == numbers[right]? 时,无法判断最小值在哪个区间之中,此时让 right–,缩小区间范围,在下一轮进行判断
    为什么是 right-- 缩小范围,而不是 left++?
    因为数组是升序的,所以最小值一定靠近左侧,而不是右侧
    比如,当存在 [1,2,2,2,2] 这种情况时,left = 0,right = 4,mid = 2,数值满足 numbers[mid] == numbers[right] 这个条件,如果 left++,则找不到最小值

    代码

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

    剑指 Offer 17. 打印从 1 到最大的 n 位数

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

    示例 1:
    
    输入: n = 1
    输出: [1,2,3,4,5,6,7,8,9]
    

    解题方案:
    整体思路:首先求出要打印的数字范围,然后再从 1 开始打印到最大的数字
    算法流程 1

    • 初始化 sum = 1
    • 循环遍历乘 10 让 sum 变为边界值
    • 新建 res 数组,大小为 sum -1
    • 从 1 开始打印,直到 sum -1 为止
    class Solution {
        public int[] printNumbers(int n) {
            int sum = 1;
            for (int i = 0; i < n; i++) {
                sum *= 10;
            }
            int[] res = new int[sum - 1];
            for(int i = 0; i < sum - 1; i++){
                res[i] = i + 1;
            }
            return res;
        }
    }
    

    剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

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

    示例:
    
    
    输入:nums = [1,2,3,4]
    输出:[1,3,2,4] 
    注:[3,1,2,4] 也是正确的答案之一。
    

    解题方案
    思路
    标签:双指针

    • 整体思路:首先指定前指针 start 和后指针 end,然后前指针定位偶数,后指针定位奇数,定位到之后将两个值互换,直到数组遍历完成
    • 时间复杂度:O(n)O(n),空间复杂度:O(1)O(1)

    算法流程

    • 初始化前指针 start = 0,后指针 end = nums.length - 1
    • 当 start < end 时表示该数组还未遍历完成,则继续进行奇数和偶数的交换
    • 当 nums[start] 为奇数时,则 start++,直到找到不为奇数的下标为止
    • 当 nums[end] 为偶数时,则 end–,直到找到不为偶数的下标为止
    • 交换 nums[start] 和 nums[end],继续下一轮交换
    • 返回 nums,即为交换后的结果
    class Solution {
        public int[] exchange(int[] nums) {
            int start = 0;
            int end = nums.length - 1;
            while(start < end) {
                while(start < end && (nums[start] % 2) == 1) {
                    start++;
                }
                while(start < end && (nums[end] % 2) == 0) {
                    end--;
                }
                int tmp = nums[start];
                nums[start] = nums[end];
                nums[end] = tmp;
            }
            return nums;
        }
    }
    

    剑指 Offer 29. 顺时针打印矩阵

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

    示例 1:
    
    输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
    输出:[1,2,3,6,9,8,7,4,5]
    
    示例 2:
    
    输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
    输出:[1,2,3,4,8,12,11,10,9,5,6,7]
    
    

    解决方案:

    思路:
    标签:二维数组
    整体思路:循环遍历整个数组,循环中再嵌套四个循环,分别是从左至右,从上至下,从右至左,从下至上
    这几个方向,按照题意将整个数组遍历完成,控制好边界

    算法流程

    1. 题目中matrix有可能为空,直接返回空数组即可
    2. 初始化边界left、right、top、bottom四个值,初始化结果数组res和数组下表x
    3. 按照遍历方向循环取出数字放入结果数组中
    • 从左至右:遍历完成后 ++top,如果 top > bottom?,到达边界循环结束
    • 从上至下:遍历完成后 --right,如果 left > right?,到达边界循环结束
    • 从右至左:遍历完成后 --bottom,如果 top > bottom?,到达边界循环结束
    • 从下至上:遍历完成后 ++left,如果 left > right?,到达边界循环结束
    class Solution {
        public int[] spiralOrder(int[][] matrix) {
            if(matrix.length == 0) return new int[0];
            int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1, x = 0;
            int[] res = new int[(right + 1) * (bottom + 1)];
            while(true) {
                for(int i = left; i <= right; i++) res[x++] = matrix[top][i];
                if(++top > bottom) break;
                for(int i = top; i <= bottom; i++) res[x++] = matrix[i][right];
                if(left > --right) break;
                for(int i = right; i >= left; i--) res[x++] = matrix[bottom][i];
                if(top > --bottom) break;
                for(int i = bottom; i >= top; i--) res[x++] = matrix[i][left];
                if(++left > right) break;
            }
            return res;
        }
    }
    
    

    剑指 Offer 39. 数组中出现次数超过一半的数字

    数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
    你可以假设数组是非空的,并且给定的数组总是存在多数元素。

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

    摩尔投票法

    class Solution {
        public int majorityElement(int[] nums) {
            int cur = 0;
            int count = 0;
            for(int num : nums){
                if(count == 0) {
                    cur = num;
                }
                if(num == cur) {
                    count++;
                } else {
                    count--;
                }
            }
            return cur;
        }
    }
    

    剑指 Offer 45. 把数组排成最小的数

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

    示例 1:
    
    输入: [10,2]
    输出: "102"
    
    示例 2:
    
    输入: [3,30,34,5,9]
    输出: "3033459"
    

    本质上是个排序算法
    解题方案:
    思路:
    拼接数组内所有元素使所有元素使结果最小,本质上是排序
    若字符串拼接a + b > b + a,那么排序上b < a;
    根据这个规则对数组内的元素排序

    算法流程:

    1. 将数组内的元素存入字符串列表strList
    2. 根据上述排序规则,对列表进行排序
    3. 最后返回拼接的字符串
    class Solution {
        public String minNumber(int[] nums) {
            List<String> strList = new ArrayList<>();
            for (int num : nums) {
                strList.add(String.valueOf(num));
            }
            strList.sort((a, b) -> (a + b).compareTo(b + a));   // 这个排序 
            StringBuilder sb = new StringBuilder();
            for (String str : strList) {
                sb.append(str);
            }
            return sb.toString();
        }
    }
    

    剑指 Offer 53 - I. 在排序数组中查找数字 I

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

    示例 1:
    
    输入: nums = [5,7,7,8,8,10], target = 8
    输出: 2
    
    示例 2:
    
    输入: nums = [5,7,7,8,8,10], target = 6
    输出: 0
    

    该题本身的解法应该非常多

    但是二分法的变种模板很给力
    解决方案
    整体思路:

    • 因为数组本身是有序的,所以利用二分查找可以降低时间的复杂度,但是因为数组中数字存在重复,所以找到target在数组中对应的左右边界非常重要
    • 容易想到的方式是分别用二分查找的方式去查找target在数组的左边界和右边界,然后将右边界减左边界即可得到结果
    • 分别查找target左边界和右边界的逻辑会有差异,这里可以取巧,变成分别查找target -1的有边界和target的右边界,结果是一样的,但是代码可以进行复用的

    算法流程

    1. 首先初始化二分查找的左边界left = 0,右边界right = nums.length - 1
    2. 当左边界不大于右边界时进行查找
    3. 计算 mid = (left + right) / 2
    4. 如果 nums[mid] <= target,则右边界在 [mid + 1, right] 中间,令 left = mid + 12
    5. 如果 nums[mid] > target,则右边界在 [left, mid - 1] 中间,令 right = mid - 1
    边界的模板
    class Solution {
        public int search(int[] nums, int target) {
            return getRightMargin(nums, target) - getRightMargin(nums, target - 1);
        }
        int getRightMargin(int[] nums, int target) {
            int left = 0;
            int right = nums.length - 1;
            while(left <= right) {
                int mid = (left + right) / 2;
                if(nums[mid] <= target) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            return left;
        }
    }
    
    

    剑指 Offer 53 - II. 0~n-1 中缺失的数字

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

    示例 1:
    
    输入: [0,1,3]
    输出: 2
    
    示例 2:
    
    输入: [0,1,2,3,4,5,6,7,9]
    输出: 8
    

    这道题的方法很多,位运算应该是比较简单的

    位运算

    class Solution {
        public int missingNumber(int[] nums) {
            int len = nums.length;
            int xor = 0;
            for(int i = 0;i < len ; i++){
                xor ^= nums[i] ^ (i+1);
            }
            return xor;
    
        }
    }
    
    

    剑指 Offer 57. 和为 s 的两个数字

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

    示例 1:
    
    输入:nums = [2,7,11,15], target = 9
    输出:[2,7] 或者 [7,2]
    
    示例 2:
    
    输入:nums = [10,26,30,31,47,60], target = 40
    输出:[10,30] 或者 [30,10]
    

    解题方案
    思路

    
        标签: 双指针
        整体思路:因为数组本身是有序的,那么完全可以在数组的开头 start 和结尾 end 位置各设置一个指针,
        通过二者的加和 sum 来找到目标值 target,如果 sum < target,则 start++,
        这样可以让下一次的 sum 变大,如果 sum > target,则 end--,这样可以让下一次的 sum 变小,找到结果
        时间复杂度:O(n)O(n)O(n),空间复杂度:O(1)O(1)O(1)
    
    算法流程
    
        首先初始化 start = 0,end = nums.length - 1,作为双指针
        当 start < end 时,始终进行循环遍历
        计算 sum = nums[start] + nums[end],并将 sum 与 target 进行比较
        如果 sum < target,则需要将下一次的 sum 值变大,因为数组有序,故而 start++
        如果 sum > target,则需要将下一次的 sum 值变小,因为数组有序,故而 end--
        如果 sum == target,则找到了最终的结果,将结果返回即可
    
    
    class Solution {
        public int[] twoSum(int[] nums, int target) {
            int start = 0;
            int end = nums.length - 1;
            while(start < end) {
                int sum = nums[start] + nums[end];
                if(sum < target) {
                    start++;
                } else if(sum > target) {
                    end--;
                } else {
                    return new int[] { nums[start], nums[end] };
                }
            }
            return new int[0];
        }
    }
    
    

    剑指 Offer 57 - II. 和为 s 的连续正数序列

    输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
    序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

    示例 1:
    
    输入:target = 9
    输出:[[2,3,4],[4,5]]
    
    示例 2:
    
    输入:target = 15
    输出:[[1,2,3,4,5],[4,5,6],[7,8]]
    
    
    class Solution {
        public int[][] findContinuousSequence(int target) {
            int left = 1;
            int right = 2;
            List<int[]> res = new ArrayList<>();
    
            while (left < right) {
                int sum = (left + right) * (right - left + 1) / 2;
                if (sum == target){
                    int[] arr = new int[right - left + 1];
                    for (int k = left; k <= right; k++) {
                        arr[k - left] = k;
                    }
                    res.add(arr);
                    left++;
                }
                else if (sum < target) {
                    right++;
                }
                else {
                    left++;
                }
            }
    
            return res.toArray(new int[res.size()][]);
        }
    }
    
    

    剑指 Offer 58 - I. 翻转单词顺序

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

    示例 1:
    
    输入: "the sky is blue"
    输出: "blue is sky the"
    
    示例 2:
    
    输入: "  hello world!  "
    输出: "world! hello"
    解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
    
    示例 3:
    
    输入: "a good   example"
    输出: "example good a"
    解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
    

    双指针

    算法流程:

    1. 首先将原始字符串去掉开头和结尾的空格得到 tmp,便于之后直接从单词处理开始
    2. 初始化单词起始位置 start 和单词结束位置 end 指针,位置在字符串结尾处
    3. 初始化结果字符串 res 为空字符串
    4. 当 start >= 0 时,说明字符串未遍历结束,作为循环条件
    5. 在 tmp[start] 位置如果不为空格,说明还没有获取到完整的单词,则 start–
    6. 获取到完整单词之后,截取 [start+1, end+1] 这一段字符串加入结果字符串中,反转单词
    7. 在 tmp[start] 位置如果为空格,说明还没有到下一个单词的结尾位置,则 start–
    8. 到单词结尾位置之后,end = start,往复进行上述流程,将单词全部反转
    9. 将结果字符串 res 去掉开头和结尾多余的空格
    class Solution {
        public String reverseWords(String s) {
            String tmp = s.trim();
            int start = tmp.length() - 1;
            int end = tmp.length() - 1;
            String res = "";
            while(start >= 0) {
                while(start >= 0 && tmp.charAt(start) != ' ') {
                    start--;
                }
                res += tmp.substring(start + 1, end + 1) + " ";
                while(start >= 0 && tmp.charAt(start) == ' ') {
                    start--;
                }
                end = start;
            }
            return res.trim();
        }
    }
    

    剑指 Offer 58 - II. 左旋转字符串

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

    示例 1:
    
    输入: s = "abcdefg", k = 2
    输出: "cdefgab"
    
    示例 2:
    
    输入: s = "lrloseumgh", k = 6
    输出: "umghlrlose"
    
    

    算法流程:

    1. 初始化结果字符串 res = “”,获取字符串长度 len
    2. 从下标 n 开始遍历,遍历到字符串 s 结尾,将区间 [n, len] 的字符添加到 res 中
    3. 从下标 0 开始遍历,遍历到下标 n 位置,将区间 [0, n] 的字符添加到 res 中
    class Solution {
        public String reverseLeftWords(String s, int n) {
            String res = "";
            int len = s.length();
            for(int i = n; i < len; i++) {
                res += s.charAt(i);
            }
            for(int i = 0; i < n; i++) {
                res += s.charAt(i);
            }
            return res;
        }
    }
    
    

    剑指 Offer 61. 扑克牌中的顺子

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

    示例 1:
    
    输入: [1,2,3,4,5]
    输出: True
    
     
    
    示例 2:
    
    输入: [0,0,1,2,5]
    输出: True
    

    在这里插入图片描述
    方法一:

    • 遍历五张牌,遇到大小王(即0)直接跳过
    • 判别重复:利用Set实现遍历判重,Set的查找方法的时间复杂度为O(1)
    • 获得最大/最小的牌:借助辅助变量ma和mi,遍历统计即可

    // 思路非常简洁

    class Solution {
        public boolean isStraight(int[] nums) {
            Set<Integer> repeat = new HashSet<>();
            int max = 0, min = 14;
            for(int num : nums) {
                if(num == 0) continue; // 跳过大小王
                max = Math.max(max, num); // 最大牌
                min = Math.min(min, num); // 最小牌
                if(repeat.contains(num)) return false; // 若有重复,提前返回 false
                repeat.add(num); // 添加此牌至 Set
            }
            return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
        }
    }
    

    方法二:
    主要的思想是:排序 + 遍历

    • 先对数组执行排序。
    • 判别重复: 排序数组中的相同元素位置相邻,因此可通过遍历数组,判断 nums[i]=nums[i+1]是否成立来判重。
    • 获取最大 / 最小的牌: 排序后,数组末位元素 nums[4]nums[4]nums[4] 为最大牌;
      元素 nums[joker],为最小牌,其中 joker 为大小王的数量。
    class Solution {
        public boolean isStraight(int[] nums) {
            int joker = 0;
            Arrays.sort(nums); // 数组排序
            for(int i = 0; i < 4; i++) {
                if(nums[i] == 0) joker++;                                    // 统计大小王数量
                else if(nums[i] == nums[i + 1]) return false;      // 若有重复,提前返回 false
            }
            return nums[4] - nums[joker] < 5;                         // 最大牌 - 最小牌 < 5 则可构成顺子
        }
    }
    
    

    剑指 Offer 66. 构建乘积数组

    给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

    解题方案
    思路

    标签:数组遍历
    整体思路:
        这道题如果可以使用除法,那么就很简单了,先求出来所有数的乘积,然后再依次除掉每个对应的值即可
        不让使用除法,那么最简单的思路就是将B[i]每个位置都把所有需要的数乘一遍,但是这样的时间复杂度非常高
        降低时间复杂度的方式就是以A[i]为界线,分割出左右三角形,其中每个三角形从尖部到底部都是可以累积的,这样就可以减少时间复杂度(具体见画)
    复杂度:
        时间复杂度:O(n)O(n)O(n)。因为左右三角遍历求乘积的时间复杂度都是O(n)O(n)O(n)
        空间复杂度:O(1)O(1)O(1)。不将结果数组算入的话,只需要常量的空间复杂度
    

    算法流程

    首先申请结果数组 res
    求出左侧三角从上到下的值,依次存入 res[i] 中
    求出右侧三角从下到上的值,并且和之前的 res[i] 做乘积存入,即可得到结果
    
    示例:
    
    输入: [1,2,3,4,5]
    输出: [120,60,40,30,24]
    
    class Solution {
        public int[] constructArr(int[] a) {
            int[] res = new int[a.length];
            int left = 1;
            for (int i = 0; i < a.length; i++) {
                res[i] = left;
                left *= a[i];
            } 
            int right = 1;
            for (int i = a.length - 1; i >= 0; i--) {
                res[i] *= right;
                right *= a[i];
            }
            return res;
        }
    }
    

    剑指 Offer 67. 把字符串转换成整数

    写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
    首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
    当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

    该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
    注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

    在任何情况下,若函数不能进行有效的转换时,请返回 0。

    说明:
    
    假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [?231,  231 ? 1]。如果数值超过这个范围,请返回  INT_MAX (231 ? 1) 或 INT_MIN (?231) 。
    
    示例 1:
    
    输入: "42"
    输出: 42
    
    示例 2:
    
    输入: "   -42"
    输出: -42
    解释: 第一个非空白字符为 '-', 它是一个负号。
         我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
    
    示例 3:
    
    输入: "4193 with words"
    输出: 4193
    解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
    
    示例 4:
    
    输入: "words and 987"
    输出: 0
    解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
         因此无法执行有效的转换。
    
    示例 5:
    
    输入: "-91283472332"
    输出: -2147483648
    解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 
         因此返回 INT_MIN (?231) 。
    
    
    class Solution {
        public int strToInt(String str) {
            char[] c = str.trim().toCharArray();
            if(c.length == 0) return 0;
            int res = 0, boundry = Integer.MAX_VALUE / 10;
            int start = 1, sign = 1;
            if(c[0] == '-') sign = -1;
            else if(c[0] != '+') start = 0;
            for(int i = start; i < c.length; i++) {
                if(c[i] < '0' || c[i] > '9') break;
                if(res > boundry || res == boundry && c[i] > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
                res = res * 10 + (c[i] - '0');
            }
            return sign * res;
        }
    }
    
    

    02 栈和队列

    剑指 Offer 06. 从尾到头打印链表

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

    示例 1:
    
    输入:head = [1,3,2]
    输出:[2,3,1]
    

    解题思路:
    使用栈特有属性
    关键点在于会使用栈的几个常用的方法进行操作

    class Solution {
        public int[] reversePrint(ListNode head) {
            Stack<ListNode> stack = new Stack<>();       
            while(head != null){
                stack.push(head);
                head = head.next;
            }
    
            int len = stack.size();
            int[] res = new int[len];
            for(int i = 0 ;i < len ; i++){
                 res[i] = stack.pop().val;
            }
            return res;
        }
    }
    

    剑指 Offer 09. 用两个栈实现队列

    用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

    示例 1:
    
    输入:
    ["CQueue","appendTail","deleteHead","deleteHead"]
    [[],[3],[],[]]
    输出:[null,null,3,-1]
    
    示例 2:
    
    输入:
    ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
    [[],[],[5],[2],[],[]]
    输出:[null,-1,null,null,5,2]
    

    解题思路:

    Deque -> 这是一个接口
    线性结合,支持两端的元素插入和移除。Deque是double ended queue的简称,习惯上称之为双端队列
    interface Deque<E>
    子接口:
    BlockingDeque<E>

    实现类

    1. ArrayDeque
    2. ConcurrentLinkedDeque
    3. LinkedBlockingDeque
    4. LinkedList
      https://www.jianshu.com/p/d78a7c982edb // 可以详细看一下
    class CQueue {
        Deque<Integer>  stack1 ;
        Deque<Integer>  stack2 ;
    
        public CQueue() {
            stack1 = new LinkedList<Integer>();
            stack2 = new LinkedList<Integer>();
        }
        
        public void appendTail(int value) {
            stack1.push(value);
        }
        
        public int deleteHead() {
            if(stack2.isEmpty()){
                while (!stack1.isEmpty()) {
                    stack2.push(stack1.pop());
                }
            }
            if (stack2.isEmpty()) {
                return -1;
            } else {
                int dele = stack2.pop();
                return dele;
            }
              
        }
    }
    

    剑指 Offer 30. 包含 min 函数的栈

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

    示例:
    
    MinStack minStack = new MinStack();
    minStack.push(-2);
    minStack.push(0);
    minStack.push(-3);
    minStack.min();   --> 返回 -3.
    minStack.pop();
    minStack.top();      --> 返回 0.
    minStack.min();   --> 返回 -2.
    

    该题比较简单,使用辅助栈即可

    算法流程

    1. MinStack 构造函数中初始化数据栈 stack1 和辅助栈 stack2
    2. push 函数中将 x 正常添加到 stack1 中,如果 stack2 为空或者 stack2 栈顶值大于等于 x 时,则将 x 加入 stack2 中,这样保证了 stack2 中的值一定是降序的,存储的数量会小于等于 stack1
    3. pop 函数中首先 stack1 需要将值 pop 出去,如果 stack2 栈顶数据与 stack1 栈顶数据相等,则将 stack2 的值也 pop 出去,保证数据栈和辅助栈的数据一致性
    4. top 函数则直接取 stack1 栈顶值即可