算法学习day23

一、k个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

 思路:

1.首先得到链表的长度size;然后在size>=k的范围里面进行翻转长度为k的链表。

2.while(size>=k) 在这个循环中,我们需要知道翻转的起点和终点 以及下一次翻转的起点。

pre(起点的前一个) start(起点) end(终点) newStart(新的起点) 并且使用cur节点为每次循环存储新的起点。

3.反转链表的步骤:

   3.1.找到前一个节点pre 

   3.2.找到end,end.next=null(断开链表)

   3.3 然后reverse(start) 翻转之后start变成尾结点 end变成头结点。

   3.4 重新连接 pre.next=end; pre=start; start.next=newStart cur=newStart

 代码:
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (k == 1) return head;
        ListNode dummyHead = new ListNode();
        dummyHead.next = head;

        ListNode pre = dummyHead;
        ListNode cur = head;//指向当前节点

        int size = getSize(cur);

        while (size >= k) {
            ListNode start = cur;
            ListNode end = cur;

            //end指向k范围内的最后一个
            for (int i = 1; i < k; i++) {
                end = end.next;
            }
        
            //下一个头结点=end.next
            ListNode nextGroupHead = end.next;

            //断开链条
            end.next = null;

            reverseLinkedList(start);

            pre.next = end;//end变成头结点
            start.next = nextGroupHead;

            pre = start;
            cur = nextGroupHead;

            size -= k;
        }
        return dummyHead.next;
    }

    public void reverseLinkedList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;

        while (curr!= null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
    }

    public int getSize(ListNode head) {
        int size = 0;
        while (head!= null) {
            size++;
            head = head.next;
        }
        return size;
    }
}

二、最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
思路:

使用哈希表对元素进行处理,先把数组中的元素都加到哈希表中

遍历表中的每一个元素,如果存在contains(num-1),直接continue;

如果num-1不存在,说明这个元素是它最长连续序列的开始,从这里开始计算。

while(set.contains(cur)){cur++;}然后Math.max(res,cur-num+1);

时间复杂度为:O(n)

代码:
class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> set=new HashSet<>();
        for(int num:nums){
            set.add(num);
        }
        int res=0;
        for(int num:set){
            //如果集合里面不含有把它小一的元素的话 那么就要从它开始计数
            int cur=num;
            if(!set.contains(cur-1)){
                while(set.contains(cur+1)){
                    cur++;
                }
                res=Math.max(res,cur-num+1);
            }
        }
        return res;
    }
}

筛除素数的方法:

数字包括素数和合数。合数:除了能被1和本身整除外、还能被其他数整除的数。

1.埃氏筛法:

时间复杂度为O(n*loglogn

思想:

1.使用boolean[]数组来对数字进行标记,如果是true表示是非质数,false表示是质数。

2.boolean[0]==boolean[1]=true;(0和1都不是质数)。

3.从2开始遍历,如果遇到boolean[i]==false,表示这个数为质数,那么就要对以该数为因数的合数置true。举个例子:遍历到3的时候,是一个质数。3*3,3*4,3*5,3*6 ...。以它为因数的合数都要置为true。为什么从3*3开始,不从2开始。因为遍历到2这个质数的时候,已经算过了

代码:
    int n = 100000;
    boolean[] prime = new boolean[n + 1];
    {
        // 埃氏筛 时间复杂度为O(n*log(logn))
        for (int i = 2; i <= n; i++) {
            if (!prime[i]) {
                // 如果遇到质数
                for (int j = i; j <= n / i; j++) {
                    prime[i * j] = true;
                }
            }
        }
        prime[0] = prime[1] = true;
    }

三、统计不是特殊数字的数字数量
 

给你两个 正整数 l 和 r。对于任何数字 xx 的所有正因数(除了 x 本身)被称为 x 的 真因数

如果一个数字恰好仅有两个 真因数,则称该数字为 特殊数字。例如:

  • 数字 4 是 特殊数字,因为它的真因数为 1 和 2。
  • 数字 6 不是 特殊数字,因为它的真因数为 1、2 和 3。

返回区间 [l, r] 内 不是 特殊数字 的数字数量。

题意简化:

找l->r中,除了本身之外因数数量2 的数字。哪些数字符合这样的规则:质数的平方数。

那么本题就是要在l->r中找某个数字的平方根是质数。

思路:

首先将l->r中的平方根范围取出来,如果x是平方数,那么sqrt(x)就是边界。如果sqrt(x)不是整数,那么就要向上取整。右边界应该是向下取整。

代码:
class Solution {
    int n = 100000;
    boolean[] prime = new boolean[n + 1];
    {
        // 埃氏筛 时间复杂度为O(n*log(logn))
        for (int i = 2; i <= n; i++) {
            if (!prime[i]) {
                // 如果遇到质数
                for (int j = i; j <= n / i; j++) {
                    prime[i * j] = true;
                }
            }
        }
        prime[0] = prime[1] = true;
    }

    public int nonSpecialCount(int l, int r) {
        if (r == 1)
            return 1;
        int total = r - l + 1;
        int left = (int)Math.ceil(Math.sqrt(l));
        int right = (int)Math.floor(Math.sqrt(r));
        System.out.println("left:"+left+"right:"+right);

        int res = 0;
        for (int i = left; i <= right; i++) {
            int square=i*i;
            if (!prime[i])
                res++;
        }
        return total - res;
    }
}
补充:如何去判断一个数字是完全平方数

int value=(int)Math.sqrt(i);

return value*value==i;

四、平方数之和(双指针法)

题意:

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c 。

思路:

1.使用双指针法,最开始a和b的范围应该是0和sqrt(c);

2从两边不断向里边逼近,当a越接近与b的时候,a^2+b^2是最大的。

3.如果遇到a^2+b^2>c,说明b要往左移动减小总和;如果遇到a^2+b^2<c,说明a要往右移动增加总和;如果相等,直接返回true

代码:
class Solution {
    public boolean judgeSquareSum(int c) {
        int a=0;
        int b=(int)Math.floor(Math.sqrt(c));//取c的根 并且向下取整
        while(a<=b){
            if(a*a==c-b*b)return true;
            else if(a*a>c-b*b)b--;
            else a++;
        }
        return false;
    }
}

五、快乐数(快慢指针/哈希表)

   快乐数  定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。
  • 1 <= n <= 231 - 1
思路:

1.对于一个n,下一个数最大为810。(因为n最大为十位数,假设每一位置上都为9,也是9*9*10=810)

2.因此下一个数只能从1-810的范围里面出现,如果重复出现了,那么必然走到了循环里。就看重复出现之前有没有出现1。

3.这种思路可以借助于HashSet来实现,也可以通过快慢指针判断是否有环。

HashSet
class Solution {
    public boolean isHappy(int n) {
        HashSet<Integer> set=new HashSet<>();
        while(n!=1&&!set.contains(n)){
            set.add(n);
            n=happyNum(n);
        }
        return n==1;
    }
    public int happyNum(int num){
        int res=0;
        while(num>0){
            res+=Math.pow(num%10,2);
            num/=10;
        }
        return res;
    }
}
快慢指针:
class Solution {
    public boolean isHappy(int n) {
        int slow=n,fast=happyNum(n);
        while(slow!=fast){
            slow=happyNum(slow);
            fast=happyNum(happyNum(fast));
        }
        return slow==1;
    }
    public int happyNum(int num){
        int res=0;
        while(num>0){
            res+=Math.pow(num%10,2);
            num/=10;
        }
        return res;
    }
}

六、哈希表一一映射关系 (单词规律/同构字符串)

单词规律:

给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。

pattern = "abba"                    s ="dog dog dog dog"   这样会出现多对一的情况

pattern = "aaaa"                    s ="dog fish  fish dog"   这样会出现一对多的情况

思路:

因此只建立一个map集合是无法正确映射他们的关系的,可能会出现一对多或者多对一的情况。好的办法就是建立两个map集合,实现一一映射的关系。

eg:pattern=abba s=dog dog dog dog;a不在map1中,添加<a,dog>;dog不在map2中,添加<dog,a>;b不在map1中,此时b对应的是dog。但是在map2中,dog只对应了a。因此不符合。 

代码:
class Solution {
    public boolean wordPattern(String pattern, String s) {
        if(s.length()==1)return true;
        String[] spl=s.split(" ");
        Map<String,Character> sTop=new HashMap<>();
        Map<Character,String> pTos=new HashMap<>();
        if(pattern.length()!=spl.length)return false;//如果两个长度不一样直接返回false
        for(int i=0;i<spl.length;i++){
            char ch=pattern.charAt(i);
            String str=spl[i];
            if((pTos.containsKey(ch)&&!(pTos.get(ch).equals(str)))||(sTop.containsKey(str)&&!(sTop.get(str)==ch))){
                return false;
            }
            pTos.put(ch,str);
            sTop.put(str,ch);
        }
        return true;
    }
}

七、分数到小数
 

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。

如果小数部分为循环小数,则将循环的部分括在括号内。

输入:numerator = 4, denominator = 333
输出:"0.(012)"
思路:

两个数相除结果可能有:整数、有限小数、无限循环小数。

1.先判断是否是正数if(a%b==0)return xxx

2.如果结果不是整数,那么继续判断是有限小数还是无限循环小数

   2.1 有限小数:最终a*=10 a%b==0的 比如说2/5,2*10/5=4  a%b==0;

   2.2 无限循环小数:会存在循环节的,10/6666666666

注意:

1.要判断两个数字的正负号

2.判断循环节的时候,使用哈希表进行判断。如果新计算出的余数在哈希表中出现过,那么就说明从map.get(a)。这个位置到现在是循环节。

3.String.format("%s(%s)",x,x);%s为占位符,表示转换后的格式为x(x);

代码:
/**
如何处理:先把整数处理出来,如果有余数可能是有限小数或者无限循环小数
1.先拼接整数
2.如果可以除尽 就说明是有限小数
3.不能除尽 找寻环节
 */
class Solution {
    public String fractionToDecimal(int numerator, int denominator) {
        long a=(long)numerator;
        long b=(long)denominator;
        if(a%b==0)return String.valueOf(a/b);
        StringBuilder sb=new StringBuilder();
        if(a<0^b<0)sb.append('-');
        a=Math.abs(a);
        b=Math.abs(b);
        //取整数
        sb.append(a/b).append(".");
        //取小数
        a%=b;
        //使用map集合 记录每一次余数的位置 用来判断是否存在循环节
        Map<Long,Integer> map=new HashMap<>();
        while(a!=0){
            int index=sb.length();
            map.put(a,index);
            a*=10;
            sb.append(a/b);
            a%=b;
            //如果map集合中遇到了重复的余数 说明后面的数字一定是循环的
            if(map.containsKey(a)){
                int c=map.get(a);
                return String.format("%s(%s)",sb.substring(0,c),sb.substring(c,sb.length()));
            }
        }
        return sb.toString();
    }
}

八、两个列表的最小索引总和

假设 Andy 和 Doris 想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。

你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设答案总是存在。

思路:

使用哈希表,如果另一个人喜爱的餐厅里面也包含遍历的该餐厅,那么就计算下标和。这里计算最小下标和的时候有一个小技巧:如果sum<min,清空集合:list.clear(),list.add(num),min=sum;这样保证了在集合中的元素是下标和最小的餐厅

代码:
class Solution {
    public String[] findRestaurant(String[] list1, String[] list2) {
        Map<String,Integer> map=new HashMap<>();
        for(int i=0;i<list2.length;i++){
            map.put(list2[i],i);
        }

        //将每一个餐厅的下标和都放进map中
        List<String> res=new ArrayList<>();
        int min=Integer.MAX_VALUE;
        for(int i=0;i<list1.length;i++){
            if(map.containsKey(list1[i])){
                int index=i+map.get(list1[i]);
                if(index<min){
                    res.clear();
                    res.add(list1[i]);
                    min=index;
                }else if(index==min){
                    res.add(list1[i]);
                }
            }
        }

        return res.toArray(new String[res.size()]);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值