算法学习之数组系列小结

数组

350题 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。给定两个数组,编写一个函数来计算它们的交集。

示例1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

示例2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

解题思路:

  1. 使用map: 遍历第一个数组并存入map中,键为数组的值,值为出现的次数。然后遍历第二个数组,如果map中有这个键,就将键对应的值减一。
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Map<Integer, Integer> map = new HashMap();

        for(int num : nums1){ //将num1放入map
            map.put(num,map.getOrDefault(num,0)+1);
        }

        int k = 0;
        for(int num : nums2){
            if(map.containsKey(num)){
                if(map.get(num) > 0){
                    map.put(num,map.getOrDefault(num,0)-1);
                    nums2[k++] = num; //将满足条件的数放到nums2开头
                }
            }
        }
        return Arrays.copyOfRange(nums2, 0, k);
    }
}
  1. 先排序,然后双指针,如果不相等,将元素小的指针右移,相等就一起右移。
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);

        int l1 = 0, r1 = nums1.length, l2 = 0, r2 = nums2.length,k = 0;

        while(l1 < r1 && l2 < r2){ //如果有一个数组遍历完就可以结束了
            if(nums1[l1] < nums2[l2]){
                l1++;
            }else if(nums1[l1] > nums2[l2]){
                l2++;
            }else{
                nums2[k++] = nums1[l1++]; ///同样用nums2接收,也可以新建一个数组
                l2++;
            }
        }
        return Arrays.copyOfRange(nums2,0,k);
    }
}

附加: 判断一个数组是不是另一个数组的子集,排序+双指针,解法相似。

14题 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。
示例 1:

输入: ["flower","flow","flight"]
输出: "fl"

示例 2:

输入: ["dog","racecar","car"]
输出: ""

解题思路:
因为要找所有字符公共前缀,那么所有字符的公共前缀肯定小于等于任意两个字符的公共前缀。因此,只需要依次比较任意两个字符的前缀,再拿这个前缀和后面的比,最后得到的字符就是公共前缀。

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs == null || strs.length == 0) return "";
        String pre = strs[0]; //pre接收公共前缀的返回结果

        for(int i = 1; i < strs.length; i++){
            pre = cut(pre,strs[i]);
            if(pre.length() == 0) break;  //提前结束
        }
        return pre;
    }

    public static String cut(String str1, String str2){ //返回任意两个字符的公共前缀
        int len = Math.min(str1.length(), str2.length());
        int index = 0;
        while(index < len && str1.charAt(index) == str2.charAt(index)){
            index++;
        }
        return str1.substring(0,index);
    }
}
189题 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]

示例 2:

输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]

解题思路:

  1. 元素全部反转,再反转前k个,后lengh-k个
  2. 暴力,每次将最后一个移到前面
  3. 额外数组
public 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[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }
}
27题 原地删除

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

示例:

给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

解题思路:
双指针,这里的双指针和上面有一点不一样,让一个指针在数组上面跑,另外一个指针重新从0开始,并且过滤掉一些重复数据。

class Solution {
    public int removeElement(int[] nums, int val) {
       int j = 0;
       for(int i = 0; i < nums.length; i++){
           if(nums[i] != val){
               nums[j++] = nums[i];
           }
       }
       return j;
    }
}
26题 删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
示例:

给定数组 nums = [1,1,2], 
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

附加: 前面两道题这种双指针的题还有第283题,都是在原地删除。

解题思路:
和上面那道题相似,去掉重复项,还是使用双指针。

class Solution {
    public int removeDuplicates(int[] nums) {
        int j = 0;
        for(int i = 1; i < nums.length; i++){
            if(nums[j] != nums[i]){
                nums[++j] = nums[i];
            }
        }
        return j+1;
    }
}
66题 加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。
示例:

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。

解题思路:
两种情况:1、除9之外的数字加1。 2、9加1
只需要判断有没有进位并模拟出它的进位方式,如十位数加 11 个位数置为 00,如此循环直到判断没有再进位就退出循环返回结果
然后还有一些特殊情况就是当出现 9999、999999 之类的数字时,循环到最后也需要进位,出现这种情况时需要手动将它进一位。

class Solution {
    public int[] plusOne(int[] digits) {
        for(int i = digits.length-1; i >=0; i--){
            digits[i]++;
            digits[i] %= 10;
            if(digits[i] != 0) return digits; //如果不为0,表示没有进位,直接返回
        }
		
		//如果遍历到了第一位,digits[0] = 0表示任然进位了,那么只需要将数组扩大1,并将第一位置为1即可
        digits = new int[digits.length + 1];
        digits[0] = 1;
        return digits;
    }
}
594. 最长和谐子序列

和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。

现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度。

示例 1:

输入: [1,3,2,2,5,2,3,7]
输出: 5
原因: 最长的和谐数组是:[3,2,2,2,3].

解题思路:
和谐序列中最大数和最小数只差正好为 1。

public int findLHS(int[] nums) {
    Map<Long, Integer> map = new HashMap<>();
    for (long num : nums) {
        map.put(num, map.getOrDefault(num, 0) + 1);
    }
    int result = 0;
    for (long key : map.keySet()) {
        if (map.containsKey(key + 1)) {
            result = Math.max(result, map.get(key + 1) + map.get(key));
        }
    }
    return result;
}
1题 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

解题思路:

  1. 暴力法很简单,遍历每个元素 xx,并查找是否存在一个值与 target - xtarget−x 相等的目标元素
  2. 两遍哈希,一遍哈希(两遍哈希的时候要排除是同一个数加两次的可能)
//一遍哈希
class Solution {
    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[]{i,map.get(temp)};
            }
            map.put(nums[i],i);
        }
        return null;
    }
}
15题 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解题思路:
首先,我们对数组升序排序,采取固定一个数,同时用双指针来查找另外两个数的。
其次,固定下来的数本身就大于 0,那三数之和必然无法等于 0
然后,如果和大于0,那就说明 right 的值太大,需要左移。如果和小于0,那就说明 left 的值太小,需要右移,同时性需要相同的数需要跳过。
在这里插入图片描述

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList();

        for(int i = 0; i < nums.length; i++){
            int target = 0 - nums[i];
            int l = i + 1;
            int r = nums.length - 1;
            if(nums[i] > 0) break;  //提前判断

            if(i == 0 || nums[i] != nums[i-1]){ // 保证和上次枚举的数不同
                while(l < r){
                    if(nums[l] + nums[r] == target){
                        ans.add(Arrays.asList(nums[i], nums[l], nums[r]));
                        //跳过相同元素
                        while(l < r && nums[l] == nums[l+1]) l++;
                        while(l < r && nums[r] == nums[r-1]) r--;
                        l++;
                        r--;
                    }else if(nums[l] + nums[r] < target){
                        l++;
                    }else
                        r--;
                }
            }
        } 
        return ans; 
    }
}
16题 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

解题思路:
和15题很相似,这里需要改变的是:

  • 根据 sum = nums[i] + nums[start] + nums[end] 的结果,判断 sum 与目标 target 的距离,如果更近则更新结果 ans。
  • 同时判断 sum 与 target 的大小关系,因为数组有序,如果 sum > target 则 end- -,如果 sum < target 则 start++,如果 sum == target 则说明距离为 0 直接返回结果。
class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int ans = nums[0] + nums[1] + nums[2];
        for(int i=0;i<nums.length;i++) {
            int start = i+1, end = nums.length - 1;
            while(start < end) {
                int sum = nums[start] + nums[end] + nums[i];
                if(Math.abs(target - sum) < Math.abs(target - ans))
                    ans = sum;
                if(sum > target)
                    end--;
                else if(sum < target)
                    start++;
                else
                    return ans;
            }
        }
        return ans;
    }
}
6题 Z字打印

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:

L   C   I   R
E T O E S I I G
E   D   H   N

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。

示例:

输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"

解题思路:
给了行数,实际就是将这个字符串按行数从上到下,再从下到上放到对应的行上面,最后将这些行的字符串合并。
从左到右迭代 s,将每个字符添加到合适的行。可以使用当前行i当前方向flag这两个变量对合适的行进行跟踪。

class Solution {
    public String convert(String s, int numRows) {
        if(numRows < 2) return s;
        List<StringBuilder> list = new ArrayList();
        for(int i = 0; i < numRows; i++){
            list.add(new StringBuilder());
        }
        int i = 0, flag = -1;
        for(char c : s.toCharArray()){
            list.get(i).append(c);
            if(i == 0 || i == numRows - 1) flag = -flag;
            i += flag;
        }

        StringBuilder res = new StringBuilder();
        for(StringBuilder row : list) res.append(row);
        return res.toString();
    }
}
54题 螺旋打印

leet连接 这个题主要用left,right,up,down四个方向来控制,主要考验coding能力。

附加

给定一个无序数组a[]和一个指定值sum,求满足a的和为sum的最长子数组的长度。

输入: a[] = 1 3 5 3 6 8 4 2 2 5
      sum = 8
输出: 3
提示:满足的最长子数组为 4 2 2
import java.util.HashMap;
import java.util.Map;

public class _01_Array_maxLength {
    public int maxLength(int[] arr, int k) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(0, -1);
        int len = 0;
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
            if (map.containsKey(sum - k)) {
                len = Math.max(i - map.get(sum - k), len);
            }
            if (!map.containsKey(sum)) {
                map.put(sum, i);
            }
        }
        return len;
    }
}
有序矩阵

有序矩阵指的是行和列分别有序的矩阵。一般可以利用有序性使用二分查找方法。

1-n分布题型(接下来三题)

645题 错误的集合

集合S包含从1到n的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。
给定一个数组nums代表了集合S发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
示例:

输入: nums = [1,2,2,4]
输出: [2,3]

解题思路:
有一个出现重复的元素,有多种解法

  1. 暴力,对于1-n的每个数字,都去遍历集合,去看看当前数字是否出现了两次。使用 dupdup 和 missingmissing 记录重复数字和缺失数字。
  2. 排序,排序nums数组后,相等的两个数字将会连续出现。此外,检查相邻的两个数字是否只相差 1 可以找到缺失数字。
  3. map,检查 11 到 nn 的每个数字在 mapmap 中出现次数。如果一个数字在 mapmap 中没有出现,它就是缺失数字。如果一个数字的出现了两次,它就是重复数字。也可以换成数组
class Solution {
    public int[] findErrorNums(int[] nums) {
        Map<Integer, Integer> map = new HashMap();
        int dup = -1, missing = -1;
        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        for(int i = 1; i <= nums.length; i++){
            if(map.containsKey(i)){
                if(map.get(i) == 2){
                    dup = i;
                }
            }else{
                missing = i;
            }
        }
        return new int[]{dup, missing};
    }
}
448题 找到所有数组中消失的数字

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。

示例:

输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]

解题思路:
这道题可以和上面一样,先放入map,然后遍历1-n来判断。对于1-n的题型还有另外的解法(原地修改):

  1. |nums[i]|-1 索引位置的元素标记为负数。即nums[|nums[i] |- 1] * -1。
  2. 然后遍历数组,若当前数组元素 nums[i] 为负数,说明我们在数组中存在数字 i+1
class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> res = new ArrayList();
        for(int i = 0; i < nums.length; i++){
            int index = Math.abs(nums[i]) - 1;
            if(nums[index] > 0) nums[index] *= -1; // 改变nums[i]原来位置上的值为负
        }

        for(int i = 1; i <= nums.length; i++){ //遍历1-n找不为负数的数字
            if(nums[i - 1] > 0){
                res.add(i);  //添加的是索引
            }
        }
        return res;
    }
}
442题 数组中重复的数字

给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。找到所有出现两次的元素。

示例:

输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]

解题思路:
这道题可以和上面一样,先放入map,然后遍历1-n来判断。

  1. 找到数字i时,将位置i-1处的数字翻转为负数。
  2. 如果位置i-1上的数字已经为负,则i是出现两次的数字。
class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        List<Integer> res = new ArrayList();
        for(int i = 0; i < nums.length; i++){
            int index = Math.abs(nums[i]) - 1; //这里就是找这个nums[i]应该放在的索引位置
            if(nums[index] < 0){
                res.add(Math.abs(index + 1)); //这个索引位置加1就是1-n中对应的值
            }
            nums[index] *= -1;
        }
        return res;
    }
}
287题 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

解题思路:
这里并没有说1-n的每个数都会出现,也有可能全部是一样的数,那么上面的1-n方法在这里无效。

  1. 二分查找
  2. 快慢指针

leet官方解答

小结

就目前上面的题来看,解决数组问题的时候先想想以下方法能不能解决:

  1. map
  2. 排序+双指针
  3. 反转
  4. 数学运算
  5. list
  6. 对于1-n类型题的原地修改
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值