每日算法——最长连续序列,合并链表,合并k个链表等

在这里插入图片描述
啊啊啊这道题我居然做过,我一点印象都没有了。
先来看一下目标,找连续的序列,连续的意味着 最小值+长度-1 = 最大值,且中间值相差为1.
这题有三种思路或者说两种。
1.动态规划:
像这种最长序列啊,子串啊,之类的第一反应就是动态规划啦

比较常规,比起暴力多了一个判断,只有当它前一个值不存在时才重看(因为这是可能的最长开始),一直查找到它的最长的情况,然后换下一个数,
这种算法表面上是O(n^2),其实是O(n),添加到set中,n次,
然后之后只有可能只会在出现新的可能的开始点时,才会执行操作,对应的操作就是n次,也就是说总共2n次。
这种操作我感觉还是有点动态规划的思想:
如果常规的数组定义:
dp[i][j] = 第i个数以前,遍历到j为止的长度情况
不过因为是连续的,所以不会出现覆盖这些情况,可以优化为dp[i]
2.转移方程:
dp[i] = if dp[i-1]==0? (j=i+1)while(dp[j++] != 0) {dp[i]++}
3.初始化
将能够命中的值初始化为1
dp[nums] = 1;
不过这种做法很麻烦(比如负数的情况,需要0偏移最小负数的绝对值),而且去重得自己手动加判断不重复判断

这里实现了>=0的版本
class Solution {
    public int longestConsecutive(int[] nums) {
        if(nums.length<=1){
            return nums.length;
        }
        int[] dp = new int[Arrays.stream(nums).max().getAsInt()+2];
        IntStream.of(nums).boxed().forEach(i->dp[i] = 1);
        int ans = 0;
        for(int i:nums){
            if((i==0||dp[i-1]==0)&&dp[i]!=1){
                int j = i+1;
                while(dp[j]!=0){
                    dp[i]++;
                    j++;
                }
               ans =  Math.max(dp[i],ans);
            }
        }
        return ans;
    }
}

所以还是用set来存储吧

class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> set = new HashSet<>();
        IntStream.of(nums).boxed().forEach(set::add);
        int ans = 0;
        for(int i:set){
           int count =1;
           if(!set.contains(i-1))
           while(set.contains(i+1)){
               i++;
               count++;
           }
           ans = Math.max(ans,count);
        }
        return ans;
    }
}

还有一种思路,
dp[i]的状态定义为dp[i] = i数左右相邻的连续数字之和最大为多少。
转移方程:
dp[i] = dp[i+1]+dp[i-1]+1
同时需要更新当前的边界值
dp[i-dp[i-1]] = dp[i];
dp[i+dp[i+1]]=dp[i];
初始化:无
不过由于刚才所说的负数等情况,所以使用了HashMap。

class Solution {
    public int longestConsecutive(int[] nums) {
        Map<Integer,Integer> map = new HashMap<>();
        int ans = 0;
        for(int i:nums){
            if(!map.containsKey(i)){
                int left = map.getOrDefault(i-1,0);
                int right = map.getOrDefault(i+1,0);
                map.put(i,left+right+1);
                map.put(i-left,left+right+1);
                map.put(i+right,left+right+1);
            }
            ans = Math.max(ans,map.get(i));
        }
        return ans;
    }
}

还有一种思路就是使用并查集,这个数据结构有点意思,一个数组或者map也好的结构,不过一个元素的value会是另一个元素的key。在这道题里面,我那个按顺序的序列正好就可以满足并查集的结构(可以想象成上是相互连接的图)。

在这里插入图片描述
首先将值转换为60的余数,且满足两两之和为60,由于每首歌都是不一样的,所以假设余数i+j=60,则size(i)*size(j) = 对数,而相同时间的比如0和0,30和30,则为(size(i)*size(i-1))/2

class Solution {
    public int numPairsDivisibleBy60(int[] time) {
        int[] r = new int[60];
        IntStream.of(time).forEach(i->r[i%60]++);
        int ans  = (r[0]*(r[0]-1))>>1;
        for(int i=1;i<30;i++){
            int min = r[i]*r[60-i];
            ans+=min;
        }
        ans += (r[30]*(r[30]-1))>>1;
        return ans;
    }
}

俺又看到一种聪明的算法:
这样少一了一次遍历30,机智机智。

class Solution {
    public int numPairsDivisibleBy60(int[] time) {
        int[] dp = new int[60];

        int res = 0;
        for(int num : time){
            num %= 60;
            res += dp[num == 0 ? 0 : 60 - num];
            dp[num]++;
        }
        return res;
    }
}

在这里插入图片描述
这道题好像是第一题?
遍历数组,就用个HashMap存一下原值即可(值为索引),如果发现新的值的差值在HashMap中已经存在过了,则返回当前索引和HashMap中对应的值

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

在这里插入图片描述
这个就是归并排序的治的过程,不过数组变成了链表而已,从自顶向下的思路,假设A链表长度为a,B:b,则a+b=最后的长度,那么哪个节点应该在哪个位置呢?首先是排好序的,再次合并的时候,只需要将两者比较小的那个留在本位置就行了,然后小的那个指针后移,重复之前操作,直到某个链表为空。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }
        if(l1.val<l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

在这里插入图片描述
上一题的升级版,不过现在是归并排序的并的完整过程。
同样用递归自顶向下的方式,从索引最右边开始合并,两两合并,然后等待当前的mid以左两两合并,然后再等待mid以左同样的过程,最后合并为一条链

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
            if(lists == null || lists.length == 0) return null;
            return merge(lists.length-1,0,lists);
        }
        private ListNode  merge(int right,int left,ListNode[] lists){
            if(left>=right){
               return lists[left];
            }
            int mid = (left+right)>>1;
            ListNode leftL = merge(right,mid+1,lists);
            ListNode rightL = merge(mid,left,lists);
            return merge(rightL,leftL);
        }
          private ListNode merge(ListNode node1, ListNode node2) {
            if(node1==null)
                return node2;
            if(node2==null)
                return node1;
            if(node1.val<node2.val){
                node1.next = merge(node1.next,node2);
                return node1;
            }else{
                node2.next = merge(node1,node2.next);
                return node2;
            }
        }
}

在这里插入图片描述
这是一道数学题,求x2最接近a的x,比较暴力的方式就是到2~a/2之前逐一查找,不过这个是有顺序的,可使用二分查找法。

class Solution {
 public int mySqrt(int a) {
        if(a<=1)
            return a;
        long left = 1,right = a>>1;
        while(left<right){
            long mid = (left+right+1)>>1;
            if(mid*mid==a){
                return (int)mid;
            }
            if(mid*mid>a){
                right = mid-1;
            }else{
                left = mid;
            }
        }
        return  (int)left;
    }

}

也阔以用牛顿迭代法:
转自https://leetcode-cn.com/problems/sqrtx/solution/er-fen-cha-zhao-niu-dun-fa-python-dai-ma-by-liweiw/
(上图来源)

class Solution {
 public int mySqrt(int a) {
        long x = a;
        while (x * x > a) {
            x = (x + a / x) / 2;
        }
        return (int) x;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值