LeetBook初级算法

                                                                                        盛夏未至,山河已秋

数组

一、删除有序数组的重复项

  • 题目描述:给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。将最终结果插入 nums 的前 k 个位置后返回 k 。不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

概述:按照原来的顺序在数组中不重复的元素移动到数组的前面。
在这里插入图片描述

  • 解题思路:

利用双指针,如果左右指针的数值相同,那么只需要移动右指针即可;如果两个指针的数值不同,那么就移动左指针,再将右指针的数值赋值给左指针,最后再移动右指针。

  • 代码部分:
class Solution {
    public int removeDuplicates(int[] nums) {
        //数组为空,直接返回0
        if(nums.length < 0) {
            return 0;
        }
        //设置左右指针
        int left = 0;
        for(int right = 1; right < nums.length; right++){
            //如果右指针的数与左指针不同,那么就移动左指针,并将右指针的值赋值给左指针
            if(nums[left] != nums[right]){
                left++;
                nums[left] = nums[right];    
            }            
        }
        return left + 1;
    }
}

二、买卖股票的最佳时机 II

  • 题目描述:给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多只能持有 一股股票。你也可以先购买,然后在同一天出售。返回你能获得的最大利润 。
    在这里插入图片描述
  • 解题思路:

贪心算法版:
假设相邻两天都会进行买卖,如果利润是正的就算到总利润里,否则就舍弃

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

动态规划版:
两个变量分别记录当前手里有股票和没股票的最大利润,根据手里股票前一天和今天的持有情况不断更新最大利润,最后返回手里没有股票的最大利润

class Solution {
    public int maxProfit(int[] prices) {
        //当前手里有股票和当前手里没股票的最大利润
        int hold = -prices[0];
        int noHold = 0;
        //利用递推公式不断更新最大利润
        for(int i = 1; i < prices.length; i++){
            hold = Math.max(hold, noHold - prices[i]);
            noHold = Math.max(noHold, hold + prices[i]);
        }
        //返回最后一天手里没有股票的最大利润
        return noHold;
    }
}

三、旋转数组

  • 问题描述:给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
    在这里插入图片描述
  • 解题思路:

设置一个翻转的方法,调用这个方法完成整体翻转和局部翻转即可

  • 代码部分:
class Solution {
    public void rotate(int[] nums, int k) {
        //因为可能旋转几个来回
        k %= nums.length;
        //整体翻转,部分翻转
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1); 
    }

    public void reverse(int [] arr, int start, int end){
        while(start < end){
            //交换
            int temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;

            //更新索引
            start++;
            end--;
        }
    }
}

四、存在重复元素

  • 问题描述:给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
    在这里插入图片描述
  • 解题思路:

(1)版本一:排序+遍历,过了但很慢 19ms
(2)版本二:遍历添加到集合中,通过数组大小和集合大小来判断 14ms
(3)版本三:在向集合中添加元素的时候就判断 4ms

版本一

class Solution {
    public boolean containsDuplicate(int[] nums) {
        //版本一:排序+遍历,过了但很慢 19ms
        Arrays.sort(nums);
        for(int i = 0; i < nums.length - 1; i++){
            if(nums[i + 1] == nums[i]){
                return true;
            }
        }
        return false;
	}
}

版本二

class Solution {
    public boolean containsDuplicate(int[] nums) {
    	//版本二:遍历添加到集合中,通过数组大小和集合大小来判断
        Set<Integer> set = new HashSet<Integer>();
        for(int i = 0; i < nums.length; i++){
            set.add(nums[i]);
        }
        //说明有重复的元素
        if(set.size() < nums.length){
            return true;
        }
        return false;
    }
}

版本三

class Solution {
    public boolean containsDuplicate(int[] nums) {
    	//版本三:在向集合中添加元素的时候就判断
    	Set<Integer> set = new HashSet<Integer>();
        for(int i : nums){
        	//添加失败,说明当前要添加的元素已经存在
            if(!set.add(i)){
                	return true;
            }
        }
        return false;
    }
}

在这里插入图片描述
通过Set的add方法源码可以看出,返回值为布尔类型,所以如果当前要添加的元素已经存在于集合中,就会导致添加失败,返回 false

五、只出现一次的数

  • 题目描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
    在这里插入图片描述
  • 解题思路:

方案一:哈希表查重【效率较低】

class Solution {
    public int singleNumber(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        for(int i : nums){
        	//将元素添加到集合中
            if(!set.add(i)){
            	//如果已经存在,那么就将这个元素删除
                set.remove(i);
            }
        }
        //最后将Object类型的集合强转为数组,剩下的唯一的元素就是答案
        return  (int)set.toArray()[0];
    }
}

在这里插入图片描述
方案二:分组遍历 【效率中等】

class Solution {
    public int singleNumber(int[] nums) {
       int index = 0;
       int len = nums.length;
       //将数组排序
       Arrays.sort(nums);
       //遍历数组
       while(index < len - 2){
       	   //两个一组,如果该组两个数不相同,那么答案就是该组第一个数
           if(index < len - 2 && nums[index] == nums[index + 1]){
               index += 2;
           }else{
               break;
           }
       }
       //返回答案
       return nums[index];
    }
}

在这里插入图片描述

方案三:异或运算【效率较高】

class Solution {
    public int singleNumber(int[] nums) {
        int res = 0;
        // a ^ a = 0、 a ^ 0 = a
        for(int i : nums){
            res ^= i;
        }
        return res;
    }
}

在这里插入图片描述

六、两个数组的交集 II

  • 问题描述:给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
    在这里插入图片描述
  • 解题思路:

将一个数组存储到哈希表(散列桶)中,key 对应元素,value 对应该元素出现的次数
再遍历另一个数组,如果该数组中元素在哈希表中存在,那么就将该元素添加到结果数组中,并将该元素在哈希表中对应的value减一
最后可以重新创建一个数组获取结果,也可以利用Arrays.copyOfRange()方法

  • 代码部分:
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        //将其中一个数组存储到哈希表中
        Map<Integer, Integer>  map = new HashMap();
        for(int i = 0; i < nums1.length; i++){
            map.put(nums1[i], map.getOrDefault(nums1[i], 0) + 1);
        }
        //创建临时数组与数组索引
        int [] res = new int[nums1.length];
        int index = -1;
        //遍历长数组,将元素添加到结果集中
        for(int i : nums2){
            if(map.containsKey(i) && map.get(i) > 0){
                res[++index] = i;
                map.put(i, map.get(i) - 1);
            }
        }
        //创建数组保存实际的结果
        // int [] result = new int[index + 1];
        // for(int i = 0; i <= index; i++){
        //     result[i] = res[i];
        // }
        //返回结果
        return Arrays.copyOfRange(res, 0, index + 1);
        //return result;
    }
}

结尾两种方式的效率是十分接近的。
在这里插入图片描述

七、加一

  • 题目描述: 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。
    在这里插入图片描述
  • 解题思路:

从最低位开始计算,如果加一后的值小于等于九,那么就更新这个最低位,直接返回原数组
如果产生进位,那么就将该位记为零,并通过循环对其高位再次进行运算,并记录产生进位的次数
如果进位次数小于数组长度,则直接返回原数组,否则就要创建一个新数组返回

  • 代码部分:
class Solution {
    public int[] plusOne(int[] digits) {
        //统计产生多少次进位
        int num = 0;
        //遍历数组
        for(int i = digits.length - 1; i >= 0; i--){
        	//无进位
            if(digits[i] + 1 <= 9){
                digits[i]++;
                break;
            }else{
            	//产生进位
                digits[i] = 0;
                num++;
            }
        }
        //原数组大小不够
        if(num == digits.length){
            int [] res = new int[num + 1];
            res[0] = 1;
            return res;
        }
        return digits;
    }
}

在这里插入图片描述

八、移动零

  • 问题描述: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作
    在这里插入图片描述
  • 解题思路:

方案一:双指针,寻找零与非零位置【效率较低】
方案二:双指针,寻找非零位置【效率较高】

☀️ 方案一

class Solution {
    public void moveZeroes(int[] nums) {
        //只有一个元素
        if(nums.length == 1){
            return;
        }
        //设置双指针
        int left = 0;
        int right = 0;
        //遍历数组
        while(left < nums.length){
            //找到左指针位置(零)
            while(left < nums.length && nums[left] != 0){
                left++;
            }
            //找到右指针位置(非零)
            right = left + 1;
            while(right < nums.length && nums[right] == 0){
                right++;
            }
			//交换值与指针位置
            if(right < nums.length){                
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                left++;    
            }else{
            	//达到了数组末尾
				return;
			}                        
        }
    }
}

在这里插入图片描述

☀️ 方案二

class Solution {
    public void moveZeroes(int[] nums) {
        //只有一个元素
        if(nums.length == 1){
            return;
        }
        //设置双指针
        int left = 0;
        int right = 0;
        //遍历数组
        while(right < nums.length){
        	//右指针寻找非零数,与左指针交换,右指针到达尾部循环结束
            if(nums[right] != 0){
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                //每交换一次左指针移动一步 
                left++;
            }
            right++;            
        }
    }
}

在这里插入图片描述

九、两数之和

  • 问题描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

  • 解题思路:

最容易理解的方式:双层for循环去遍历 【方法一】
优化版本:利用哈希表的key-value,简化为一次for循环 【方法二】

🐟方法一

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //双层遍历
        for(int i = 0; i < nums.length; i++){
            int num = target - nums[i];
            for(int j = i + 1; j < nums.length; j++){
                if(nums[j] == num){
                    return new int []{i, j};
                }
            }
        }
        return null;
    }
}

在这里插入		图片描述
🐟方法二

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //利用哈希表,因为需要返回下标,所以得用散列桶
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        //遍历数组
        for(int i = 0; i < nums.length; i++){
            if(map.containsKey(target - nums[i])){
                return new int []{i, map.get(target - nums[i])};
            }else{
                //如果对应结果不存在,就将当前元素添加到哈希表中
                map.put(nums[i], i);
            }
        }
        return null;        
    }
}

在这里插入图片描述

十、有效的数独

  • 问题描述:
    在这里插入图片描述

在这里插入图片描述

  • 解题思路:

设置三个二维数组,分别记录行、列、小矩形中的元素【易知:九行九列九个小矩形 10个元素】
双层for循环确定行和列,那么小矩形的索引如何确定呢?
巧妙运用数学公式: j / 3 + (i / 3) * 3就可以根据当前行列确定为第几个盒子

在这里插入图片描述

  • 代码部分:
class Solution {
    public boolean isValidSudoku(char[][] board) {
        //利用三个二维数组分别记录 行、列、盒子 【9行、9列、9个盒子、10个数】
        int [][] row = new int[9][10];
        int [][] coloum = new int[9][10];
        int [][] box = new int[9][10];
        //遍历数组
        for(int i = 0; i < 9; i++){
            for(int j = 0; j < 9; j++){
                //获取当前位置的数
                if(board[i][j] == '.'){
                    continue;
                }
                //将字符转化为整数
                int num = board[i][j] - '0';
                //判断是否满足规则
                if(row[i][num] == 1){
                    return false;
                }
                if(coloum[j][num] == 1){
                    return false;
                }
                int index = j / 3 + (i / 3) * 3;
                if(box[index][num] == 1){
                    return false;
                }
                //更新该元素在数组中的记录
                row[i][num] = 1;
                coloum[j][num] = 1;
                box[index][num] = 1;
            }
        }
        //返回结果
        return true;
    }
}

在这里插入图片描述

十一、旋转图像

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

(1)采用翻转两次,就可以达到旋转图像的目的
(2)也可以采用临时数组保存结果,最后赋值给原数组

💙 方法一

class Solution {
    public void rotate(int[][] matrix) {
        //先上下翻转,再主对角线翻转
        int n = matrix.length;
        //上下翻转
        for(int i = 0; i < n/2; i++){
            for(int j = 0; j < n; j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n - i - 1][j];
                matrix[n - i - 1][j] = temp;
            }
        }
        //主对角线翻转
        for(int i = 0; i < n; i++){
            for(int j = i; j < n; j++){
                if(i == j){
                    continue;
                }
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
    }
}

在这里插入图片描述
💚 方法二

class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        //临时数组
        int [][] temp = new int[n][n];
        //遍历数组
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                temp[j][n - i - 1] = matrix[i][j];
            }
        }
        //赋值
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                matrix[i][j] = temp[i][j];
            }
        }
    }
}

在这里插入图片描述

---------------------------------------------------------------------✂----------------------------------------------------------------------------------

字符串

一、反转字符串

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

遍历数组,将前半部分与后半部分的元素进行交换

  • 代码部分:
class Solution {
    public void reverseString(char[] s) {
        //遍历交换
        for(int i = 0; i < s.length >> 1; i++){
            char temp = s[i];
            s[i] = s[s.length - 1 - i];
            s[s.length - 1 - i] = temp;
        };
    }
}

在这里插入图片描述

二、整数反转

  • 问题描述:
    在这里插入图片描述

  • 解题思路:

(1)应用StringBuffer类的 reverse() 方法,此处利用捕获异常来对溢出进行处理
(2)利用数学方法进行处理

  • 代码部分:

☀️ 方法一

class Solution {
    public int reverse(int x) {
        //将x转化为StringBuffer的对象
        StringBuffer sb = x > 0 ? new StringBuffer(x + "") : new StringBuffer(Math.abs(x) + "-");
        //反转字符串
        sb.reverse();
        //保存结果
        int ans = 0;
        try{
            //如果存在溢出就捕获异常,不进行处理返回0
            ans = Integer.parseInt(sb.toString());
        }catch(Exception e){}
		//返回结果
        return ans;

    }
}

在这里插入图片描述
☀️ 方法二

class Solution {
    public int reverse(int x) {
        //保存临时结果
        long ans = 0;
        //遍历整数的每一位
        while(x != 0){
            //从末尾取数,没取一次将原来的结果扩大10倍
            ans = ans * 10 + x % 10;
            x /= 10;
        }
        //要求返回的是整数
        return (int)ans == ans ? (int)ans : 0;

    }
}

在这里插入图片描述

三、字符串中的第一个唯一字符

  • 问题描述:
    在这里插入图片描述

  • 解题思路:

(1)哈希表存储: 将字符串中的字符与出现次数存储到哈希表中,遍历字符串并查阅哈希表,如果当前字符的Value为1,那么直接返回索引,没找到返回 -1
(2)数组计数: 方法与前面的类似,只是通过数组来记录字母出现的次数

  • 代码部分:

💙 方法一

class Solution {
    public int firstUniqChar(String s) {
        //只有一个字符
        if(s.length() == 1){
            return 0;
        }        
        //创建哈希表
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        //遍历字符串,将字符与出现次数的映射存储到哈希表中
        for(char c : s.toCharArray()){
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        //遍历字符串,找到第一个唯一字符
        for(int i = 0; i < s.length(); i++){
            char ch = s.charAt(i);
            if(map.get(ch) == 1){
                return i;
            }
        }
        return -1;
    }
}

在这里插入图片描述

🖤 方法二

class Solution {
    public int firstUniqChar(String s) {
        //只有一个字符
        if(s.length() == 1){
            return 0;
        }
        
        //数组计数
        int [] count = new int[26];
        for(char c : s.toCharArray()){
            count[c - 'a']++;
        }
        //遍历字符串
        for(int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if(count[c - 'a'] == 1){
                return i;
            }
        }
        return -1;
    }
}

在这里插入图片描述

四、有效的字母异位词

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

用两个额外的数组记录对应字母的出现次数,最后再比较一下

  • 代码部分:
class Solution {
    public boolean isAnagram(String s, String t) {
        //两个字符串长度一定要相同
        if(s.length() != t.length()){
            return false;
        }
        //创建两个计数数组
        int [] arr1 = new int[26];
        int [] arr2 = new int[26];
        //遍历两个字符串,统计元素出现次数
        for(char c : s.toCharArray()){
            arr1[c - 'a']++;
        }
        for(char c : t.toCharArray()){
            arr2[c - 'a']++;
        }
        //对比
        for(int i = 0; i < 26; i++){
            if(arr1[i] != arr2[i]){
                return false;
            }
        }
        return true;

    }
}

在这里插入图片描述

五、验证回文串

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

将字符串中的数字和字母提取出来,利用了StringBuffer类字符串,然后记录当前String类字符串和反转后的字符串,比较他们的内容是否相同,相同返回 true,不同返回 false

  • 代码部分:
class Solution {
    public boolean isPalindrome(String s) {
        //创建一个StringBuffer类的对象
        StringBuffer sb = new StringBuffer();
        //将字符串中的数字和字母提取出来
        for(char c : s.toCharArray()){
            if((c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')){
                sb.append(c);
            }
        }
        //记录获取到的字符串和反转后的字符串
        String s1 = sb.toString().toLowerCase();
        String s2 = sb.reverse().toString().toLowerCase();
        //比较反转后是否相同
        if(s1.equals(s2)){
            return true;
        }
        return false;
    }
}

在这里插入图片描述

六、字符串转换整数(atoi)

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

将字符串转化为字符数组,遍历找到第一个非空元素,通过标记确定正负号,然后继续遍历,如果字符不为0-9,那么直接结束遍历。
如果超过整数范围就返回对应的最大值和最小值,否则通过 **res = res * 10 + flag * (ch[i] - ‘0’);**计算

  • 代码部分:
class Solution {
    public int myAtoi(String s) {
        //利用字符数组存储字符串
        char [] ch = s.toCharArray();
        //记录索引位置
        int index = 0;
        int len = s.length();
        //找到前面第一个非空格位置
        while(index < len && ch[index] == ' '){
            index++;
        }
        //全空格
        if(index == len){
            return 0;
        }
        //判断正负号
        int flag = 1;
        char firstChar = ch[index];
        if(firstChar == '+'){
            index++;
        }else if(firstChar == '-'){
            flag = -1;
            index++;
        }
        //保存结果
        int res = 0;
        //再次遍历添加元素
        for(int i = index; i < len; i++){
            //因为都是用对应ASCII码记录的,如果不是数组就直接返回
            if(ch[i] < '0' || ch[i] > '9'){                 
               break;                 
            }
            //判断是否越界
            if(res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE/10 && ch[i] - '0' > Integer.MAX_VALUE % 10)){
                return Integer.MAX_VALUE;
            }
            if(res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE/10 && ch[i] - '0' > -(Integer.MIN_VALUE % 10))){
                return Integer.MIN_VALUE;
            }
            res = res * 10 + flag * (ch[i] - '0');
        }        
        return res;
    }
}

七、实现strStr()

  • 问题描述
    在这里插入图片描述
  • 解题思路

利用Java提供的indexOf方法 【实际应该用KMP算法】

  • 代码部分
class Solution {
    public int strStr(String haystack, String needle) {
        //第一次出现子串的位置
        if(needle.length() == 0){
            return 0;
        }
        return haystack.indexOf(needle);
    }
}

在这里插入图片描述

八、外观数列

  • 问题描述
    在这里插入图片描述
    在这里插入图片描述
  • 解题思路

利用StringBuffer存储当前项的字符串,利用res存储前一项的字符串,通过递推方式获得结果

  • 代码部分
class Solution {
    public String countAndSay(int n) {
        //利用递推的原理
        String res = "1";
        //从第二项起,获得结果需要递推n - 1次
        for(int i = 0; i <= n - 2; i++){
            int len = res.length();
            StringBuffer sb = new StringBuffer();
			//遍历完前一项的字符串
            for(int j = 0; j < len; j++){
                int count = 1;
                while(j < len - 1 && res.charAt(j) == res.charAt(j + 1)){
                    count++;
                    j++;
                }
                //此处取的是res前一项的字符
                sb.append(count+"").append(res.charAt(j));
            }
            //获取第 i + 2 项的字符串
            res = sb.toString();
            
        }
        //返回结果
        return res;    
    }
}

在这里插入图片描述

九、最长公共前缀

  • 问题描述:
    在这里插入图片描述

  • 解题思路:

判断有几个字符串,如果只有一个,那么就直接返回这个字符串 strs[0]
存在多个字符串就找字符串的最小长度为多少,空串就代表零
设置标记和存储索引的变量,根据字符串的最小长度去对比每个字符串的对应位置元素,如果出现不同了,就更新索引和标记,然后跳出两层循环
如果索引为零或字符串最小长度为零,则直接返回空串
最后利用substring方法截取字符串作为结果返回【获得字符串的索引范围:0, index - 1

  • 代码部分
class Solution {
    public String longestCommonPrefix(String[] strs) {
        //获取字符串数组大小
        int len = strs.length;
        if(len == 1){
            return strs[0];
        }
        //设置标记
        boolean flag = true;
        int index = -1;
        //获得最小字符串的长度
        int minLen = Integer.MAX_VALUE;
        for(int i = 0; i < len; i++){
            
            minLen = Math.min(minLen, strs[i] != null ? strs[i].length() : 0);
        }
        //双层for循环
        for(int i = 0; i < minLen; i++){
            for(int j = 0; j < len - 1; j++){
                if(strs[j].charAt(i) != strs[j + 1].charAt(i)){
                    index = i;
                    flag = false;
                    break;
                }
            }
            if(!flag){
                break;
            }
        }
        //判断是否找到公共前缀
        if(index == 0 || minLen == 0){
            return "";
        }
        index = index == -1 ? minLen : index;
        //因为截取的范围为[0, index)
        String res = strs[0].substring(0, index);
        return res;
    }
}

在这里插入图片描述
---------------------------------------------------------------------✂----------------------------------------------------------------------------------

链表

一、删除链表中的结点

  • 问题描述:

在这里插入图片描述
在这里插入图片描述

  • 解题思路:

提示里面有一点很重要,每个节点的值都是唯一的
按照常规思路,我们要找到它的前驱结点,让后将前驱结点的后继指针指向当前结点的下一个结点
此处因为不能访问头结点,我们可以通过交换结点的值,来相对更换结点的位置,然后再去掉无用结点即可

  • 代码部分:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        //链表的各个结点的值是唯一的,交换结点的值就相当于更换了节点的位置
        node.val = node.next.val;
        //移动指针
        node.next = node.next.next;
    }
}

在这里插入图片描述

二、删除链表的倒数第N个结点

  • 问题描述

在这里插入图片描述

  • 解题思路

与正常删除链表中结点的思路类似,重点在于找到待删除结点的前驱结点这个要删除的结点是不是头结点还要分情况讨论,因为要求返回头结点。

  • 代码部分
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //存储结点与对应位置的关系
        ListNode [] list = new ListNode[30];
        //获取链表头结点
        ListNode pre = head;
        //记录结点的个数,并充当索引
        int index = -1;
        while(pre != null){
            list[++index] = pre;
            pre = pre.next;
        }
        //删除结点
        if(index - n + 1 > 0){
          list[index - n].next = list[index - n + 1].next;  
        }else{
            //该节点为头结点
            head = head.next;
        }
        return head;
    }
}

在这里插入图片描述

三、反转链表

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

三次遍历链表,利用数组将各个结点的值存储下来,最后再逆序赋值回去

  • 代码部分:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        //统计链表结点个数
        int count = 0;
        //获取头指针
        ListNode p = head;
        //遍历链表
        while(p != null){
            count++;
            p = p.next;
        }
        //保存链表结点值的数组
        int [] num = new int[count];
        //更新p指向表头
        p = head;
        //将链表的值存储到数组中
        for(int i = 0; i < count; i++){
            num[i] = p.val;
            p = p.next;
        }
        //更新p指向表头
        p = head;
        //再次遍历链表
        for(int i = count - 1; i >= 0; i--){
            p.val = num[i];
            p = p.next;
        }
        return head;
    }
}

在这里插入图片描述
方法二:创建新链表 【使用了额外的空间】

class Solution {
    public ListNode reverseList(ListNode head) {
        //同样是通过结点的值入手,只不过是创建一个新的链表
        ListNode res = null;
        for(ListNode p = head; p != null; p = p.next){
            res = new ListNode(p.val, res);
        }
        return res;
    }
}

在这里插入图片描述
方法三:更新指针的指向

class Solution {
    public ListNode reverseList(ListNode head) {
        //如果链表中结点个数不大于1,那么没有更新指针的必要
        if(head == null || head.next == null){
            return head;
        }
        //记录相邻两结点
        ListNode left = head;
        ListNode right = head.next;
        //对原来的头结点进行处理
        head.next = null;
        //遍历链表
        while(right != null){
            //更新相邻结点的指针
            ListNode p = right.next;
            right.next = left;
            //更新左右指针
            left = right;
            right = p;
        }
        //右结点为空,左节点到达原链表的末尾
        return left;
    }
}

在这里插入图片描述

四、合并两个有序链表

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

利用两链表中的较小值去创建一个新的链表作为结果返回

  • 代码部分:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //判断两个链表是否为空
        if(list1 == null){
            return list2;
        }else if(list2 == null){
            return list1;
        }
        //新链表头结点        
        ListNode p = list1.val > list2.val ? new ListNode(list2.val,null) : new ListNode(list1.val,null);
        ListNode res = p;
        //遍历两链表,创建新的结果链表
        while(list1 != null && list2 != null){
            if(list1.val > list2.val){
                //获取新链表的结点
                res.next  = new ListNode(list2.val);
                //更新res
                res = res.next;
                list2 = list2.next;
            }else{
                res.next = new ListNode(list1.val);
                //更新res
                res = res.next;
                list1 = list1.next;
            }
        }
        //可能其中一个链表还有剩余部分
        while(list1 != null){
           res.next = new ListNode(list1.val);
            //更新res
            res = res.next;
            list1 = list1.next; 
        }
        while(list2 != null){
            res.next = new ListNode(list2.val);
            //更新res
            res = res.next;
            list2 = list2.next;    
        }
        //返回结果,因为有个链表的头结点用了两次
        return p.next;
    }
}

在这里插入图片描述

五、回文链表

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

利用数组来存储链表的所有结点的值,再遍历数组来判断是否构成回文序列

  • 代码部分:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {  
        //结点个数不大于1
        if(head == null || head.next == null){
            return true;
        }      
        //获取链表头结点
        ListNode p = head;
        int count = 0;
        //遍历链表
        while(p != null){
            count++;
            p = p.next;
        }
        //更新p
        p = head;
        //利用数组保存链表的值
        int [] num = new int[count];
        for(int i = 0; i < count; i++){
            num[i] = p.val;
            p = p.next;
        }
        //遍历数组判断是否为回文链表
        for(int i = 0; i < (count >> 1); i++){
            if(num[i] != num[count - 1 -i]){
                return false;
            }
        }
        return true;
        
    }
}

在这里插入图片描述

六、环形链表

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

利用快慢指针,如果链表没有环,那么快指针会先为空;如果链表有环,那么两个指针终究会相遇

那么为什么有环两个指针就一定会相遇呢?

如果有环,两个指针最后都会走到环上,假设环总长为n步,而两个相距的短边为x步,他们每移动一次距离缩小一步,所以当移动x次之后,两指针一定会相遇

  • 代码部分:
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        //特殊情况
        if(head == null || head.next == null){
            return false;
        }
        //设置快慢指针
        ListNode low = head;
        ListNode fast = head;
        //遍历链表
        while(fast != null && fast.next != null){
            low = low.next;
            fast = fast.next.next;
            //两指针相遇了
            if(low == fast){
                return true;
            }
        }
        return false;
    }
}

在这里插入图片描述
也可以牺牲速度换空间,利用集合来解决,将链表的结点添加到集合中,如果集合中已经存在了当前结点,那么就会添加失败,则证明有环存在

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set set = new HashSet<ListNode>();
        while(head != null){
            if(!set.add(head)){
                return true;
            }
            head = head.next;
        }
        return false;
    }
}

在这里插入图片描述
---------------------------------------------------------------------✂----------------------------------------------------------------------------------

一、二叉树的最大深度

  • 问题描述:
    在这里插入图片描述

  • 解题思路:

采用深度优先搜索的思想,利用自底向上的方式,最大深度为左右子树的最大深度 + 1
在这里插入图片描述

  • 代码部分:
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }

        //递归左子树的最大深度
        int leftMaxDepth = maxDepth(root.left);
        //递归右子树的最大深度
        int rightMaxDepth = maxDepth(root.right);

        //二叉树的最大深度为子树的最大深度加一
        return Math.max(leftMaxDepth, rightMaxDepth) + 1;

    }
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/9dc5500490784c10bf0e1bf8053bcd1b.png)

二、验证二叉搜索树

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

因为二叉搜索树的特殊性质,中序遍历得到的是一个递增的序列,利用集合将结点值存储起来,再遍历集合检查是否严格满足递增序列【因为涉及到根据索引在集合中取值,所以我采用的是List而不是Set

  • 代码部分:
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        //只有一个结点
        if(root.left == null && root.right == null){
            return true;
        }
        //利用集合来存储二叉树结点的值
        List<Integer> set = new ArrayList<Integer>();
        //中序遍历
        inorder(root, set);
        //遍历集合
        for(int i = 0; i < set.size() - 1; i++){
            if(set.get(i) >= set.get(i + 1)){
                return false;
            }
        }
        return true;        
    }
    public void inorder(TreeNode root, List<Integer> set){
        //当前结点为空
        if(root == null){
            return;
        }
        //遍历左子树
        inorder(root.left, set);
        //添加结点值
        set.add(root.val);
        //遍历右子树
        inorder(root.right, set);
    }
}

在这里插入图片描述

三、对称二叉树

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

至少存在一个结点,所以从左右子树入手,对于左右子树是否为空进行分情况讨论,比较两个子树当前结点是否相同,然后进行递归比较

  • 代码部分:
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        //只有根结点
        if(root.left == null && root.right == null){
            return true;
        }
        //根结点的左子树
        TreeNode leftNode = root.left;
        TreeNode rightNode = root.right;

        //调用比较的方法
        return isSymmetry(leftNode, rightNode);
    }
    public boolean isSymmetry(TreeNode r1, TreeNode r2){
        //两者都为空
        if(r1 == null && r2 == null){
            return true;
        }
        //两者有一个为空
        if(r1 == null || r2 == null){
            return false;
        }
		//比较结点值
        if(r1.val != r2.val){
            return false;
        }

        //递归进入子树
        boolean leftNode = isSymmetry(r1.left, r2.right);
        boolean rightNode =  isSymmetry(r2.left, r1.right);

        return leftNode && rightNode;
    }
}

在这里插入图片描述

四、二叉树的层序遍历

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

层序遍历采用的是自左向右的顺序,利用当前索引来控制是否创建新的子集合,以及将当前的值添加到第几个子集合中

  • 代码部分:
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        //根结点为空
        if(root == null){
            return new ArrayList<List<Integer>>();
        }
        //创建一个结果集合
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        //调用遍历方法
        level(root, res, 1);
        //返回结果
        return res;
    }
    public void level(TreeNode root, List<List<Integer>> res, int index){
        //当前结点为空
        if(root == null){
            return;
        }
        //判断是否要创建下一层
        if(res.size() < index){
            res.add(new ArrayList<Integer>());
        }
        //将当前元素添加到集合中
        res.get(index - 1).add(root.val);
        //递归左子树
        level(root.left, res, index + 1);
        //递归右子树
        level(root.right, res, index + 1);
    }

}

在这里插入图片描述

五、将有序数组转化为二叉搜索树

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

二叉搜索树中序遍历得到的就是一个升序序列,所以我们不断去取数组中间的元素去构造结点,然后利用左半部分去构造左子树,又半部分去构造右子树,最后返回根结点

  • 代码部分:
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {      
        //只有一个结点
        if(nums.length == 1){
            return new TreeNode(nums[0]);
        }
        //调用方法去创建平衡的二叉搜索树
        return createTree(nums, 0, nums.length - 1);
    }
    public TreeNode createTree(int [] nums, int l, int r){
        //是否超边界
        if(l > r){
            return null;
        }
        //取中点
        int index = (l + r) / 2;
        //创建一个结点
        TreeNode root = new TreeNode(nums[index]);
        //递归左右子树
        root.left = createTree(nums, l, index - 1);
        root.right = createTree(nums, index + 1, r);
        //返回根结点
        return root;        
    }
}

在这里插入图片描述
---------------------------------------------------------------------✂----------------------------------------------------------------------------------

排序和搜索

一、合并两个有序数组

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

利用一个临时数组保存结果,不断遍历两个数组,有序取出其中的较小值,可能一个数组中有剩余元素未遍历,再将这部分添加到数组的末尾,最后将临时数组的值赋值给nums1即可。

  • 代码部分:
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //nums2为空
        if(n == 0){
            return;
        }
        //创建一个临时数组
        int [] num = new int[m + n];
        //设置索引
        int index = -1;
        int left = 0, right = 0;
        //遍历两个数组 双层for循环
        while(left < m && right < n){
            num[++index] = nums1[left] > nums2[right] ? nums2[right++] : nums1[left++];
        }
        //可能其中一个数组有剩余
        while(left < m){
            num[++index] = nums1[left++];
        }
        while(right < n){
            num[++index] = nums2[right++];
        }
        //遍历num数组
        for(int i = 0; i < m + n; i++){
            nums1[i] = num[i];
        }
    }
}

在这里插入图片描述

二、第一个错误的版本

  • 问题描述:
    在这里插入图片描述

  • 解题思路:

序列是有规律的,从第一个错误版本开始之后的版本都是错误的,为了减少接口调用次数,可以选择二分查找,为了防止溢出取中间值时用mid = left + (right - left) / 2

  • 代码部分:
/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        //false 代表没出错, true 代表出错了,所以说我们要找到第一个true
        //左右两个指针
        int left = 1, right = n;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(isBadVersion(mid)){
                right = mid;
            }else{               
                left = mid + 1;    
            }
        }
        return right;
    }
}

在这里插入图片描述

---------------------------------------------------------------------✂----------------------------------------------------------------------------------

动态规划

一、爬楼梯

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

当大于等于第三层时,当前n层次可以从n - 1 或 n-2过来,所以满足dp[i] = dp[i - 1] + dp[i - 2],dp[1] = 1、dp[2] = 2为初始值

  • 代码部分:
class Solution {
    public int climbStairs(int n) {
        //保存结果的数组
        int [] dp = new int[46];
        //初始化
        dp[1] = 1;
        dp[2] = 2;
        //遍历更新
        for(int i = 3; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

在这里插入图片描述

二、买股票的最佳时机

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

假设在最低点买入,在之后的日子里选择一个最高点卖出

  • 代码描述:
class Solution {
    public int maxProfit(int[] prices) {
        //最低价格
        int minPrice = Integer.MAX_VALUE;
        //最大利润
        int maxPro = Integer.MIN_VALUE;
        //在最低点买入
        for(int i = 0; i < prices.length; i++){
            if(prices[i] < minPrice){
                minPrice = prices[i];
            }
            if(prices[i] - minPrice > maxPro){
                maxPro = prices[i] - minPrice;
            }
        }
        //返回结果
        return maxPro > 0 ? maxPro : 0;

    }
}

在这里插入图片描述

三、最大子序和

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

要找出连续子序列的最大和,可以采用动态规划的方式,要保证子问题无后效性,也就是说之后计算出来的结果不能对之前已经得到的结果产生影响
dp[i] 代表以第i个元素结尾的最大子序和状态转移方程为与之前的最大正子序列和加和或为当前元素的值
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
初始值就是只有第一个元素时的最大值
dp[0] = nums[0];

  • 代码部分
class Solution {  
    public int maxSubArray(int[] nums) {
        //记录最大值
        int maxValue = Integer.MIN_VALUE;
        //dp[i] 代表以第i个元素结尾的最大子序和
        int [] dp = new int[nums.length];
        //完成初始化
        dp[0] = nums[0];
        //遍历数组
        for(int i = 1; i < nums.length; i++){
            //因为以nums[i]结尾,所以一定包含nums[i]
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
        }
        //遍历动态数组,找出最大值
        for(int i : dp){
            if(i > maxValue){
                maxValue = i;
            }
        }
        //返回结果
        return maxValue;
    }

}

在这里插入图片描述

四、打家劫舍

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

因为不能偷相邻的房屋,而且要偷的两个房屋之间隔几个也不确定,所以需要利用到二阶动态方程 dp[i][0] dp[i][1] 分别代表第i个房屋没偷与偷了
如果当前房屋没偷,可能前一个房屋偷了,也可能前一个房屋也没偷
dp[i][0] = Math.max(dp[i - 1][1], dp[i - 1][0]);
如果当前房屋偷了,那么前一个房屋肯定没偷
dp[i][1] = dp[i - 1][0] + nums[i];
动态方程的初始状态就是第一个房屋有没有偷
dp[0][0] = 0;
dp[0][1] = nums[0];

  • 代码部分:
class Solution {
    public int rob(int[] nums) {
        //数组只有一个元素
        if(nums.length == 1){
            return nums[0];
        }
        //创建动态数组dp[i][j],代表第i家偷了和第i家没偷的最高金额
        int [][] dp = new int[nums.length][2];
        //初始状态方程
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        //遍历数组
        for(int i = 1; i < nums.length; i++){
            //状态转移【不能偷相邻的房屋】
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = dp[i -1][0] + nums[i];
        }       
        //返回结果
        return Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]);
    }
}

在这里插入图片描述
---------------------------------------------------------------------✂----------------------------------------------------------------------------------

设计问题

一、打乱数组

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

设计一个类,该类有两个方法 reset可以返回传入的数组,shuffle可以返回打乱后的原数组。
打乱数组采用洗牌的方式,为了保证打乱的概率是相同的,采用将当前元素与之后的元素随机交换【包括当前元素】,最后返回新数组即可

  • 代码部分:
class Solution {
	//获取传入的数组
    public int [] nums;
    //利用Random类生成随机索引
    Random random = new Random();
    //利用构造器获得数组
    public Solution(int[] nums) {
        this.nums = nums;
    }
    
    public int[] reset() {
    	//直接将传入的数组返回
        return nums;
    }
    
    public int[] shuffle() {
        //获取数组长度
        int len = nums.length;
        //创建一个新的数组
        int [] arr = Arrays.copyOf(nums, nums.length);
        for(int i = 0; i < len; i++){
            int j = i + random.nextInt(len - i);
            //交换 i 和 i后面的一个随机元素
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        //返回新数组
        return arr;
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(nums);
 * int[] param_1 = obj.reset();
 * int[] param_2 = obj.shuffle();
 */

在这里插入图片描述

二、最小栈

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

利用链表来模拟栈,添加元素的时候采用头插法,对于后序查看栈顶元素、删除栈顶元素都很方便,对于栈中的最小值我们可以为链表设置一个记录最小值的元素

  • 代码部分:
class MinStack {
    //创建链表头结点
    Node head;
    public MinStack() {
        head = new Node();
    }
    
    public void push(int val) {
        //头插法创建链表
        Node cur = new Node(val);
        Node headNext = head.next;
        head.next = cur;
        cur.next = headNext;

        //记录当前结点的最小值
        if(cur.next == null){
            cur.minVal = cur.val;
        }else{
            cur.minVal = Math.min(cur.val, cur.next.minVal);
        }
        
    }
    
    public void pop() {
        //删除头结点后的第一个结点
        head.next = head.next.next;

    }
    
    public int top() {
        return head.next.val;
    }
    
    public int getMin() {       
        return head.next.minVal;
    }
}
//结点
class Node{
    public int val;
    public Node next;
    public int minVal;
    public Node(int val){
        this.val = val;
    }
    public Node(){}
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

在这里插入图片描述

---------------------------------------------------------------------✂----------------------------------------------------------------------------------

数学

一、Fizz Buzz

参考新手村题单

二、计数质数

  • 问题描述:给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
    在这里插入图片描述

  • 解题思路

(1)我们最容易想到的办法就是暴力循环,质数就是除了本身和一之外没有其他因子,但超时了,此处给出代码理解,不提倡使用
(2)埃氏筛算法,将给出的所有的数都认为是质数,然后根据倍数关系筛掉不是质数的数,重点在于边界条件的处理
当 x > 2时,我们只需要从 x*x开始即可,因为小于x方的都被2的倍数筛选掉

  • 代码部分

方法一

class Solution {
 public int countPrimes(int n) {       
  	//方法一:暴力遍历  【终究还是超时了】
    //计数
    int count = 0;
    //双层for循环
    for(int i = 2; i < n; i++){            
        if(judgePrime(i)){
            count++;
         }
    }
    //返回结果
    return count;
    }
public boolean judgePrime(int num){
    for(int j = 2; j <= Math.sqrt(num); j++){
            //不是质数
            if(num % j == 0){
                return false;
            }
        }
    return true;
}

方法二

class Solution {
    public int countPrimes(int n) { 
        //埃氏筛

        //计数
        int count = 0;
        //布尔型数组记录是否为素数
        boolean [] isPrim = new boolean [n];
        //先初始化为所有数为素数
        Arrays.fill(isPrim, true);
        //遍历数组
        for(int i = 2; i * i < n; i++){
            //筛掉当前的数的倍数
            if(isPrim[i]){
                //将其倍数置为非质数
                for(int j = i * i; j < n; j += i){
                    isPrim[j] = false;
                }
            }
        }
        //遍历isPrim数组
        for(int i = 2; i < n; i++){
            if(isPrim[i]){
                count++;
            }
        }
        //返回结果
        return count;
    }
}

在这里插入图片描述

三、3的幂

  • 问题描述:
    给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x

  • 解题思路

(1)利用集合存储一部分3的幂,检测当前输入的值是否在集合中。根据图像可以看出,3的幂增长速度越来越快,幂总是小于其开平方的运算结果【也就是说,如果传入的数为81,那么集合中记录3的0-9次幂就够了

class Solution {
    public boolean isPowerOfThree(int n) {
        //三的幂一定可以被三整除
        if(n % 3 != 0 && n != 1){
            return false;
        }
        /*
        将指定范围内的三的幂存储到集合中w
        */
        Set set = new HashSet();
        for(int i = 0; i <= Math.sqrt(n); i++){
            set.add((int)Math.pow(3, i));
        }
        if(set.contains(n)){
            return true;
        }
        return false;
    }
}

在这里插入图片描述
通过运行结果可以看出效率不高

(2) 采用连除的方法,看最后结果是否为1

class Solution {
    public boolean isPowerOfThree(int n) {
        while(n % 3 == 0 && n != 0){
            n /= 3;
        }
        return n == 1;
    }
}

在这里插入图片描述

(3)通过判断该数是否为 在整数范围内 3幂运算最大值的约数

class Solution {
    public boolean isPowerOfThree(int n) {        
        return n > 0 && (1162261467 % n == 0);
    }
}

四、罗马数字转整数

  • 问题描述
    在这里插入图片描述
  • 解题思路

方法一: 减去特殊项

将罗马数字与整数之间的映射关系,将特殊项与正常顺序项的差值记录下来,

class Solution {
    public int romanToInt(String s) {
        //保存结果
        int res = 0;
        //利用哈希表存储罗马数与数字的对应关系
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        map.put('I', 1);
        map.put('V', 5);
        map.put('X', 10);
        map.put('L', 50);
        map.put('C', 100);
        map.put('D', 500);
        map.put('M', 1000);
        //将字符串转化为字符数组
        char [] cur = s.toCharArray();
        for(int i = 0; i < s.length(); i++){
            res += map.get(cur[i]);
        }
        //检测原字符串中是否存在那几个特殊的片段
        int diff = 0;
        if(s.indexOf("IV") > -1 || s.indexOf("IX") > -1){
            diff += 2;
        }
        if(s.indexOf("XL") > -1 || s.indexOf("XC") > -1){
            diff += 20;
        }
        if(s.indexOf("CD") > -1 || s.indexOf("CM") > -1){
            diff += 200;
        }
        //返回结果
        return res - diff;
    }
}
  • 代码部分
    在这里插入图片描述

方法二:分类加和

还是利用哈希表存储,只是在进行转化时,直接与其后一位进行对比判断应该加整数,还是加负数

class Solution {
    public int romanToInt(String s) {
        //保存结果
        int res = 0;
        //利用哈希表存储罗马数与数字的对应关系
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        map.put('I', 1);
        map.put('V', 5);
        map.put('X', 10);
        map.put('L', 50);
        map.put('C', 100);
        map.put('D', 500);
        map.put('M', 1000);
        //将字符串转化为字符数组
        char [] cur = s.toCharArray();
        for(int i = 0; i < s.length() - 1; i++){
        res += map.get(cur[i]) >= map.get(cur[i+1]) ? map.get(cur[i]) : -map.get(cur[i]);
        }
        //返回结果【加上末尾的值】
        return res + map.get(s.charAt(s.length() - 1));
    }
}

在这里插入图片描述
---------------------------------------------------------------------✂----------------------------------------------------------------------------------

其他

一、位1的个数

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

因为Java没有无符号整数,所以此处不能通过字符串来判断有多少个一
可以利用Java提供的位运算:按位与
偶数(末尾为零)与 1按位与结果为0,奇数(末尾为一) 与 1
通过右移运算符,从后向前记录1的个数

  • 代码部分:
public class Solution {
    public int hammingWeight(int n) {
        //计数
        int count = 0;
        //右移32次
        for(int i = 0; i < 32; i++){
            //末尾为1
            if((n & 1) == 1){
                count++;
            }
            //右移
            n = n >> 1;
        }
        //返回结果
        return count;
    }
}

在这里插入图片描述

二、汉明距离

  • 问题描述
    在这里插入图片描述
  • 解题思路

与位一的个数思路类似,也是获取末尾的二进制数,依次比较,如果不同就将计数器加一,通过右移运算来从后向前获取末尾的二进制值

  • 代码部分
class Solution {
    public int hammingDistance(int x, int y) {
        //计数
        int count = 0;
        //通过位1的个数来确定有多少个不同的元素
        for(int i = 0; i < 32; i++){
            if((x & 1) != (y & 1)){
                count++;
            }
            x >>= 1;
            y >>= 1;
        }
        
        return count;
    }
}

三、颠倒二进制位

  • 问题描述
    在这里插入图片描述
  • 解题思路

从原无符号整数的末尾不断取二进制数,添加到结果的末尾,即可完成翻转

  • 代码部分
public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        //保存结果
        int res = 0;
        //取出给定整数的每一位
        for(int i = 0; i < 32; i++){
            //将res左移,空出低位
            res <<= 1;
            //将n的低位通过或运算添加到res中
            res |= n & 1;
            //更新n的低位
            n >>= 1;
        }
        //返回结果
        return res;
    }
}

在这里插入图片描述

四、杨辉三角

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

直接利用两个集合来处理,内层集合记录一层的元素,外层集合记录所有结果

  • 代码部分:
class Solution {
    public List<List<Integer>> generate(int numRows) {        
        //创建结果集
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        //创建杨辉三角函数的序列
        for(int i = 0; i < numRows; i++){
            //保存一行的集合
            List<Integer> temp = new ArrayList<Integer>();
            for(int j = 0; j <= i; j++){
                //杨辉三角的两边
                if(j == 0 || j == i){
                    temp.add(1);
                }else{
                    //当前元素的值为 前一行同一列 与 前一行前一列 的值加和
                    temp.add(res.get(i - 1).get(j) + res.get(i - 1).get(j - 1));
                }
                
            }
            //将每一行添加到集合中
            res.add(temp);            
        }
        //将结果添加到集合中
        return res;
    }
}

在这里插入图片描述

五、有效的括号

  • 问题描述:
    在这里插入图片描述
  • 解题思路:

利用栈来处理最合适不过了,当遇到左括号时就将其对应的右括号存储到栈中,如果遇到右括号的时候就取出栈顶元素看是否相同,如果相同就证明匹配上了,最后如果栈中为空就返回 true,否则返回 false

补充一点:对于Stack,因为Stack继承了Vector,而Vector是动态数组接口,可以动态扩容随机访问,所以这与栈的理念是违背的,在Java中Stack显示为弃用状态。
我们一般用双端队列Deque来实现栈

  • 代码部分:
class Solution {
    /**
        利用栈来存储,左括号入栈,右括号出栈对比
     */
    public boolean isValid(String s) {
        //创建辅助栈
        Deque<Character> stack = new ArrayDeque();
        //遍历字符串
        for(int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if(c == '('){
                stack.push(')');
            }else if(c == '['){
                stack.push(']');
            }else if(c == '{'){
                stack.push('}');
            }else{
                if(stack.isEmpty() || c != stack.pop()){
                    return false;
                }
            }
        }
        //字符串有效
        return stack.isEmpty() ? true : false;
    }
}

在这里插入图片描述

六、缺失数字

  • 问题描述
    在这里插入图片描述
  • 解题思路

将所有元素加和,利用等差公式计算出从1-nums.length 的和值,比较他们的差值就是缺失的数字

  • 代码部分
class Solution {
    public int missingNumber(int[] nums) {
        //所有元素加和比较差值
        int count = 0;
        int len = nums.length;
        for(int i : nums){
            count += i;
        }
        return len * (len + 1) / 2 - count;
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bow.贾斯汀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值