算法学习day25

三/四数之和类型

一、三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

最接近的三数之和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

思路:

两道题的做法都是类似的,先固定一个起始位置,然后在剩余的范围里面使用双指针进行遍历(前提是要先对数组进行排序),遍历的过程中找符合条件的结果。

注意:在三数之和这道题中,涉及到结果去重。如何去重:当遍历到的下一个数字如果和上一个数字是相同的,就直接continue; 这个是外层for循环的去重逻辑

 if (i > 0 && nums[i] == nums[i - 1])
                continue;

内层双指针的去重逻辑:
 

  
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    right--; // 移动右指针并重新计算 sum
代码:
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1])
                continue;
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    right--; // 移动右指针并重新计算 sum
                } else if (sum < 0) {
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    left++; // 移动左指针并重新计算 sum
                } else {
                    List<Integer> part = new ArrayList<>();
                    Collections.addAll(part, nums[i], nums[left], nums[right]);
                    res.add(part);
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    right--;
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    left++;
                }
            }
        }
        return res;
    }
}

二、盛水最多的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

思路:双指针

使用双指针指向容器的两边,此时围成面积的宽是最大的。在双指针不断向里面缩进的同时,宽变小了,要使面积最大,每次就要使两边的高最大。因此移动的时候,要选择高度小的那一方移动。

代码:
class Solution {
    public int maxArea(int[] height) {
        int max=Integer.MIN_VALUE;
        int left=0;
        int right=height.length-1;
        while(left<right){
            int area=(right-left)*Math.min(height[left],height[right]);
            max=Math.max(area,max);
            if(height[left]<height[right])left++;
            else right--;
        }
        return max;
    }
}

三、删除排序链表中的重复元素II

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

思路:

重复元素一个也不保留,都删除掉。判断逻辑:因为该链表是排序好的,当该元素和前一个元素、后一个元素都不相等的时候,才说明这个元素不是重复的。因此我用了pre节点、slow节点、fast节点。分别表示前一个节点,当前节点,下一次节点。

如果出现了不重复的元素:

1.可能出现在链表中间:fast.next!=null&&fast.val!=pre.val&&fast.val!=fast.next.val;

2.可能出现这链表结尾:fast.next==null&&fast.val!=pre.val

满足这样的条件之后:slow.next=fast; slow=slow.next;fast=fast.next;pre=pre.next

代码:
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummyHead=new ListNode(-101);
        dummyHead.next=head;
        ListNode fast=head;
        ListNode slow=dummyHead;
        ListNode pre=dummyHead;
        while(fast!=null){
            if(fast.next!=null&&fast.val!=pre.val&&fast.val!=fast.next.val){
                slow.next=fast;
                slow=slow.next;
                fast=fast.next;
                pre=pre.next;
            }else if(fast.next==null&&fast.val!=pre.val){
                slow.next=fast;
                slow=slow.next;
                fast=fast.next;
            }else{
                fast=fast.next;
                pre=pre.next;
            }
        }
        slow.next=null;
        return dummyHead.next;
    }
}

四、有效的三角形个数(双指针)

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

类似于三数之和:只是这个题的条件改变一下。

思路:

1.将数组进行升序排好之后,然后使用双指针(固定right 移动left)

2.如果nums[left]+nums[right]>nums[i],这样的话就找到了可以构成三角形的最小下标,此时count+=right-left;然后将right--(向左移动 看新的情况)

注意:这里向左移动之后,left是不用重新归位的,因为左移之后的总和是<=之前的总和的,因此从left这个位置继续向右移动,寻找>nums[i]的最小下标

3.如果<=的话,left继续向右移动(因为是升序的,left移动的同时,总和肯定是会变大的)

代码:
class Solution {
    public int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        int count=0;
        for(int i=nums.length-1;i>=2;i--){
            int left=0;
            int right=i-1;
            while(left<right){
                if(nums[left]+nums[right]<=nums[i]){
                    left++;
                }else{
                    count+=right-left;
                    right--;
                }
            }
        }
        return count;
    }
}

五、重复的DNA序列(Set集合)

DNA序列 由一系列核苷酸组成,缩写为 'A''C''G' 和 'T'.。

  • 例如,"ACGAATTCCG" 是一个 DNA序列 。

在研究 DNA 时,识别 DNA 中的重复序列非常有用。

给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。

示例 1:

输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC","CCCCCAAAAA"]
思路:

1.可以利用set集合元素唯一的特性去做这道题,遍历String字符串,每次截取10个添加到set集合中。如果add()方法返回的是true,说明是第一次添加;如果返回的false,说明是重复出现的;

2.使用哈希表map<String,Integer>。key:value字符串:出现的次数。遍历字符串,每十个添加到哈希表中,然后遍历一遍把出现次数>1的收集到list中。

代码:
class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        Map<String,Integer> map=new HashMap<>();
        List<String> res=new ArrayList<>();
        for(int i=0;i<=s.length()-10;i++){
            String str=s.substring(i,i+10);
            map.put(str,map.getOrDefault(str,0)+1);
        }
        Set<String> keys=map.keySet();
        for(String str:keys){
            if(map.get(str)>1)res.add(str);
        }
        return res;
    }
}

六、无重复字符的最长子串()

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串的长度
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 “abc"
思路:

使用双指针进行遍历,每次左边固定,右边寻找无重复子串的最长子串。

当遇到 map.containsKey(s.charAt(right))的时候,说明遇到重复子串了,此时更新一下当前子串的长度,然后将左边起始位置右移,继续遍历。

没有遇到重复字符的话,就把字符添加到map集合中,

代码:
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer> map=new HashMap<>();
        int max=0;
        int res=0;
        int left=0;
        int right=left;
        while(left<=right&&right<s.length()){
            char ch=s.charAt(right);
            if(map.containsKey(ch)){
                map=new HashMap<>();
                left++;
                right=left;
            }else{
                right++;
                map.put(ch,map.getOrDefault(ch,0)+1);
            }
            res=Math.max(res,right-left);
        }
        return res;
    }
}

滑动窗口(将顺序转换为数量和种类)

七、字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。

换句话说,s1 的排列之一是 s2 的 子串 。

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
思路:

0.要判断s2是否包含s1的排列,顺序不一样也算包含,直观的想法:就是将s1所有的排序都列出来,然后判断在s2中是否存在,存在一种就返回true;但是这种方法会超时。

1.如何在s2中找出s1的排列?如果s2中有子串的长度和字符的个数都和s1相同,那么就意味着s2中有s1的排列。

2.如何判断s2中是否拥有连续的和s1中相等的字符和数量?可以借助数组,字母一共有26个,因此如果字符的种类和个数是一样的话,那么数组也一定相同。通过Arrays.equals(arr1,arr2);

3.arr1就是s1串中字符的种类和数量,那么arr2中应该放什么?利用滑动窗口

  3.1滑动窗口的大小为s1串的长度时候,就要判断arr2和arr1是否相等;

  3.2如果大于s1串长度的时候,左边界就要往左移动(也就是数组中左边界那个字符的次数减一); 

  3.3如果小于s1串长度的时候,右边界就要向右移动,每次在arr2中新增的字符次数+1;

代码:
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        //赊账本
        int[] arr1=new int[26];
        int len1=s1.length();
        for(int i=0;i<len1;i++){
            int index=s1.charAt(i)-'a';
            arr1[index]++;
        }
        //记账本
        int[] arr2=new int[26];
        int startIndex=0;
        for(int i=0;i<s2.length();i++){
            if(i>=len1){  
                arr2[s2.charAt(i-len1)-'a']--;
            }
            int index=s2.charAt(i)-'a';
            arr2[index]++;
            if(Arrays.equals(arr1,arr2))return true;
        }
        return false;
    }
}

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值