力扣刷题手册

前言: 随缘刷题,认真记录
偶尔刷一刷力扣的每日一题,写完做笔记
每天都在为写出优雅的代码努力!

1、字符串相加

8/3力扣415字符串相加.

用字符串的方法来模拟加法过程,使用双指针从尾部开始遍历,逐个相加:
需要解决的问题:

  1. 进位。
  2. 两数长短不一致。

解决方案:
进位问题: 用一个变量记录每次相加中有没有进位,如果进位了该位置1,将加到下一个两两相加的结果中。
两数长度不一样: 有一个数已经到达数组头部时(已经加完了) 将其设为0,依然相加。

class Solution {  
    public String addStrings(String num1, String num2) {
        StringBuffer res =new StringBuffer() ;
        char[] arr1=num1.toCharArray();
        char[] arr2=num2.toCharArray();

        int i=arr1.length-1,j=arr2.length-1,tmp=0,sum=0; 

        while(i>=0 || j>=0 || sum!=0 ) { 
            int b =( (j<0)?0:arr2[j]-'0') ;
            int a= ( (i<0)?0:arr1[i]-'0'); 
            tmp=a+b+sum; 
            sum=tmp/10; 
            res.append(tmp%10) ;
            i--; j--; 
        }
        
        res.reverse() ;
        return res.toString();
    }
}

变量解析:
sum标志有没有进位,可以发现在while的条件判断中也加入了sum!=0的判断。

如果"9"和"1"相加,虽然两者长度相同,sum不为0时,依然需要多循环一次,完成下一位进一的动作。

tmp需要加入结果集,需要保证它只能是一位,%10可以保证其个位

做题感想:
巧妙使用变量表达每一次循环的状态,避免了经常使用if判断的冗余。学习把!
以前也有做过模拟大数相加的题,是以链表数据结构为载体的,当时没做出来,还是超出了范围,挖个坑,日后补上。

最后:
题目中提到了BigInteger这个类,随便了解一下:
java.math.BigInteger
java.math.BigInteger用来表示任意大小的整数(通常是超出long型的)。BigInteger内部用一个int[]数组来模拟一个整数。

两个BigInteger的运算或一个基本运算都需要使用方法。

它的构造函数有:
BigInteger(byte[] val)
BigInteger(String val)
BinInteger(String val, int radix) 等等

一些方法
BigInteger abs() 返回大整数的绝对值
BigInteger add(BigInteger val) 返回两个(this和val) 大整数的绝对值
BigInteger and(BigInteger val) : 返回两个大整数的按位与的结果

8.10更新 :

前言: 昨天写了2道题,记录了3种解法,结果在最后的时刻直接关了电脑,啥都没了。。 还能怎样,重写吧。。 以后决定写一题更新一次/(ㄒoㄒ)/~~

2、K次操作转字符串

K次操作转变字符串
在这里插入图片描述

解题思路

  1. 这道题目就是要做一件事: 用一定的规则将一个字符替换成字母表中的另一个字母。
  2. i次操作,可以将 一个字母ch变换成 字母表ch+i位置上的字母。 计算出s和t同一位置上的字符相差的距离distance,和k的大小作比较即可。
  3. 要注意的是: 从a换到b, 可以替换1次,27次,52次… 可以找到它的规律: distance+26*(freq-1) : distance就是a到b的距离。 freq, 也就是相同距离的一对字母的出现的频率
class Solution {
    public boolean canConvertString(String s, String t, int k) {
        if(s.length()!=t.length())return false;
        int []cnt=new int[26];
        
        char str[]=s.toCharrArray();
        char tr []=t.toCharrArray();
        int distance;
        
        for(int i=0;i<str.length;i++){
            distance=t[i]-s.[i];
            if(distance<0)distance+=26;
            cnt[distance]++;
        }
        
        int max =0;
        int idx=0;
        for(int i=1;i<cnt.length;i++){
            if(cnt[i]>=max ){
                max=cnt[i];
                idx=i;
            }
        }
        return 26*(max-1)+idx<=k;  //将出现相同距离的最大频率和k相比。 
    }
}

数组 cnt[distance]=freq

3、找出数组游戏的赢家

8/12 力扣1535.
在这里插入图片描述

  • 这道题一个迷惑说明是 “较小的整数移至数组的末尾”,可以试想如果每次输掉一个数字就要将它移到最后一个位置代价有多大,直接硬模拟是不可能的

  • 思路: 不需要直接移到最后,只需要维护两个指针,一直指向当前的要比较的两个数就可以了。

想到这里,就好办了,(我还想了一下,有没有可能在之前被丢弃的数字,会变成最后的赢家呢? 答案应该是不可能的。)

//我的ac代码。 
class Solution {
    public int getWinner(int[] arr, int k) {
        int zero=0, one=1 , count =0 ,winner = 0 ,len=arr.length ;
        while(one<len&&count<k){
            if(arr[zero]>arr[one]) {
                one++ ;
                count++ ;
                winner = arr[zero] ;
            }else{
                winner =arr[one] ;
                zero=one++; 
                count=1 ;
            }
        }
        return winner;
    }
}
//最后的赢家在k>1的情况下都是处于0的位置上的,所以,如果当前一轮比较是0号赢了,将count++, 如果是1号赢了,将重置count为1。 



//在题解上看到一些优秀的题解,在此搬运学习下
class Solution {
    public int getWinner(int[] arr, int k) {
        int win=arr[0],count=0;/*win:胜利者,count:获胜场次*/
        for(int i=1;i<arr.length&&count<k;i++){/*到达获胜场次跳出循环*/
            if(arr[i]<win){/*arr[i]比win小*的情况*/
                count++;
            }else{/*如果win输掉了产生新的胜利者,count置为1*/
                win=arr[i];
                count=1;
            }
        }
        return win;/*返回win*/
    }
}

作者:zhttty-2
链接:https://leetcode-cn.com/problems/find-the-winner-of-an-array-game/solution/zhi-xing-yong-shi-1-ms-zai-suo-you-java-ti-jia-278/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4、粉刷天花板

8/12 领扣281.
题目本身不难,但是如果硬肝,会导致超时,就像我随便乱写的代码,就超时了,也想不到怎么优化。 还是参考了题解:
用了双指针,觉得有点不明觉厉(不明白为什么我的就超时了)

public long painttheCeiling(int s0, int n, int k, int b, int m, long a) {
         long  arr[] = new long  [n] ;
        arr[0]=s0;
        long count=0; 
        for(int i=1 ;i<n ;i++){
            arr[i] =(k*arr[i-1]+b)%m +1 + arr[i-1] ;
        }//计算得到的数组本身递增。 
        int right=arr.length-1;
        for(int i=0;i<n;i++){
        //Ac代码和我的超时版本差异就是这个for循环的里层
            while(right>=0&&arr[right]*arr[i]>a) {
                right-- ;
            }
            count+=(right+1) ;
        }
        return count ;
    }

指针right从最大的乘以最小的,并记录不符合题意的个数,当while循环结束后,right+1就是当前i位置上符合题意的个数。

5、整理字符串

8/14 力扣1544.

在这里插入图片描述

解题思路(一)
直接在原字符串的基础上删除掉相邻一对字母。

要注意的是如果为"DABbaCcd"删掉Bb后会让Aa也成为符合条件的一对。除此之外,这道题就没有其他难点了。

为了防止漏删,一种解决方式是一旦删掉一对字母,就回到字符串的开头,重新遍历一次。

class Solution {
    public String makeGood(String s) {
        if(s.equals("") ||s.length()<2||s==null) return s; 
        StringBuffer  ans= new StringBuffer(s); 
        for(int i=0;i<ans.length()-1 ;) {
            if( Math.abs(ans.charAt(i)-ans.charAt(i+1)) == 32) {
                ans.delete(i,i+2) ; 
                i=0; 
            }
            else 
                i++ ; 
        }
        return ans.toString() ; 
    }
}

在评论区还能看到一些有意思的解法,本质上都是检查本次 “make good” 有没有更新的,如果有更新,就说明需要再check一次了。

解题思路二: 利用栈
将字符串一一放入栈中,当栈的长度大于等于2的时候,就开始检查栈顶两个元素是否符合条件,如果符合,就移除。

class Solution {
    public String makeGood(String s) {
        if(s.equals("") ||s.length()<2||s==null) return s; 

        StringBuffer  stack= new StringBuffer(); 
        
        for(int i=0;i<s.length();i++){
            stack.append(s.charAt(i)) ;
            int len = stack.length() ;
           
            if(len>=2 && Math.abs(stack.charAt(len-1)-stack.charAt(len-2) )==32 )
                stack.delete(len-2,len) ; 
        }
        return stack.toString() ; 
    }
}

6、 可以到达的最远建筑

力扣1642.

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

这道题,被我误判成dp了。。 我的思路是:这不就是重叠子问题嘛,每过一个比它高的都有2种选择:1,放砖头2,放梯子。
但事实上,人家用的贪心+优先队列,接下来来记录解题思路:

梯子可以用来跨越很大的gap,而砖头用一块少一块,我们的策略就是,尽量将梯子放在大gap上用,小的就用砖头 。所以我们可以选择用梯子或者砖头一直往前走,直到不够用了,才用和刚才相反的物品替换掉之前的选择,来实现我们的贪心策略,同时用优先队列,记录我们走过的gap的大小

讲到这里,估计就明确了思路,但是在编码上我们要有选择,是记录大的gap,还是记录小的gap呢? 比较好的应该是记录小的gap,为什么? 因为,记录小的gap,意味着我们默认用的都是梯子;记录大的gap,默认用的是砖头来记录,而梯子是一个一个的,砖头是几块几块的用,会在编码上带来麻烦。

class Solution {
    public int furthestBuilding(int[] heights, int bricks, int ladders) {
        // 先用砖头,等到砖头不够用了,再回去看,把之前用的最多砖头的地方用梯子替换掉,再继续往前走,到最后砖头和梯子都不够用就是最远距离、
        int l=heights.length;
        
        //创建大顶堆
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        
        for(int i=0;i<l-1;i++){
            int dis = heights[i+1]-heights[i];
            if(dis>0) 
                queue.offer(dis) ; // 默认使用梯子,使用的梯子数就是size 
            if(queue.size() > ladders) 
                bricks -=queue.poll() ; // 当梯子数不够了,才使用砖头 
            if(bricks<0)  // 不能用else if
                return i ; // 砖头也不够用了,就是最大距离
        }
        return l-1;
    }
}

代码里面有一些新东西,比如说,它没有显式地减ladder的值,ladder不够是用 queue.size() > ladders 来表现的,也就减少了多余的代码。

7、分割链表

力扣725.分割链表

给你一个头结点为 head 的单链表和一个整数 k ,请你设计一个算法将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。

这 k 个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。
返回一个由上述 k 部分组成的数组。

示例 1:
在这里插入图片描述
输入:head = [1,2,3], k = 5
输出:[[1],[2],[3],[],[]]
解释: 第一个元素 output[0] 为
output[0].val = 1 ,output[0].next = null 。 最后一个元素 output[4] 为 null,但它作为 ListNode 的字符串表示是 [] 。 示例 2:

输入:head = [1,2,3,4,5,6,7,8,9,10], k = 3
输出:[[1,2,3,4],[5,6,7],[8,9,10]]
解释: 输入被分成了几个连续的部分,并且每部分的长度相差不超过 1。前面部分的长度大于等于后面部分的长度。

**解题思路:这道题比较简单,尽量平均分配,就整除len/part,除不尽的 len%part,然后剩下多余的就分到前面割出来的链表 **

废话不多说上代码:

class Solution {
    private int lenOfList(ListNode head){
        int len =0 ;
        ListNode p = head ;
        while(p!=null){
            len++ ;
            p=p.next ;
        }
        return len ;
    }
    public ListNode[] splitListToParts(ListNode head, int k) {
        int len = lenOfList(head) ,i=0 ;
        int mod = len%k,  part=len/k ;
        ListNode [] lists = new ListNode[k] ;
        ListNode p=head ,q=head ;
        for (;i<k;i++){
            if(p==null){
                // 如果已经分完了,接下来的数组元素全部置为null
                lists[i] = null ;
                continue ;
            }
            // 指针移动,先分配应该平均分的部分 
            int a= part-1 ;
            while (a>0){
                q=q.next ;
                a-- ;
            } if( part>0&& mod>0){
            //    如果有多的,再分多一个 
                q=q.next ;
                mod--;
            }
            // 新的链表已经生成了,需要断尾,然后进行下一轮扫描
            ListNode p1 = p ;
            lists[i] =p1;
            p = q.next;
            q.next = null;
            q = p;
        }
        return lists;
    }
}

一遍AC了,有点高兴,但按照老规矩,还是去膜拜下其他大佬的答案,果然官方的代码就简洁许多:
我的思路是:先分配定额的,如果有多再加,而官方的代码的做法是多出i的个肯定也是在链表数组的前i个 ,在指针移动的时候直接分配就好了:

//官方题解: 
class Solution {
    public ListNode[] splitListToParts(ListNode head, int k) {
        int n = 0;
        ListNode temp = head;
        while (temp != null) {
            n++;
            temp = temp.next;
        }
        int quotient = n / k, remainder = n % k;

        ListNode[] parts = new ListNode[k];
        ListNode curr = head;
        for (int i = 0; i < k && curr != null; i++) {
            parts[i] = curr;
            int partSize = quotient + (i < remainder ? 1 : 0);
            for (int j = 1; j < partSize; j++) {
                curr = curr.next;
            }
            ListNode next = curr.next;
            curr.next = null;
            curr = next;
        }
        return parts;
    }
}

8、 两整数之和

力扣371

给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。
示例:
输入:a = 1, b = 2
输出:3

思路:不能用+,-,那就必须是位运算啦。
动笔算算就可以发现,用二进制按位相加,有进位的进位,就可以得到答案,(负数也同理)
e.g:
2 + 3
0010 + 0011 --> 0101 为五

接下来就是两种思路了:
第一暴力模拟,可以人工计算每一位的答案,但太不优雅,马上放弃
第二种需要一些位运算的知识和思考:我也是看答案才知道的,学!

首先:我们要知道这种加法在位运算有一种很类似的,就叫做异或。异或的特点就是无进位加法。

无进位加完还不够,我们需要进位呀! 所以还需要将进位算出来, 这个也很简单,进位的条件就是两个1,用 与 &就行了。

每次无进位加完之后,我们都要把之前没加上的进位加上去,当然,如果本来就没有进位,进位等于0,那就没有加的必要了。(这里就是一个循环,终止条件是进位为0 )

到这里,我们就把一个加法拆分成了 无进位加法和加上进位两个动作。

class Solution {
    public int getSum(int a, int b) {
        while(b!=0){
            int carry = (a&b)<<1 ; //先计算进位
            a=a^b ; //无进位相加
            b =carry ;
        }
        return a ;
    }
}

在代码里可以看到,原数只要加了一次之后,接下来的结果就是和进位斗争了。

感想:这样优雅的代码,羡慕,什么时候我才能独立的写得出来?

9、数字转换为16进制数

力扣405.
今天国庆第二天,力扣搞了道简单的,将一个10进制数字弄成16进制的,简单!一上手我就给搞了一个模16取余算法,结果到负数这就卡壳了,原来人家都用了位运算,这说明我的位运算果然是💩,罢了罢了,学习叭

解题思路:
4个位一组的2进制数组成一个16进制数,我们可以通过位运算4位一组的处理这个十进制数。 从高位到低位开始处理成
根据题意不能有前导零

  • 如果是负数,第一个数字必定不是0
  • 如果是正数,就从不是0的第一组开始计算
class Solution {
   public String toHex(int num) {
       StringBuilder sb= new StringBuilder() ;
       //32位整数 8*4 
       for(int i=7;i>=0;i--){
           int val = (num>>(4*i)) & 0xf ;// 通用套路 
           if(val>0 || sb.length()>0 ) 
            {
               char digit = val<10? (char)('0'+val) : (char)('a'+val-10) ;
               sb.append(digit); 
           }
       }
       return sb.toString() ;
   }
}

另外:很多题目不是那么简单的模拟就完事了,不然要算法干嘛??没事多想想,有什么算法?而不是暴力模拟

为什么很难用编程去思考位运算,因为在课堂上的知识,都是用人工的计算方式, 但放到java这个语言上,我怎么知道 int a =10 ;它是用补码还是移码实现的? (要学啊!)

10、复杂链表的复制

剑指35.复杂链表的复制

解题思路: 对于一个普通链表,只需要从头到尾遍历一次然后建表即可,这题难点就在于链表的随机指针域。 复制过程中可能随机指针的结点还没有建立起来。

如果在用传统的遍历建表法,这道题很难解,假设我们已经复制出了一条链表, 如果我们不在乎时间空间的开销,可以尝试使用每次copy一个随机指针,我们就遍历整个链表一次;

问题在于,原表的随机指针指向的结点, 无法在新表中标识出来(可能有重复值,无法比较) , 所以,我们需要用某种数据结构,建立起某种联系。

  • 可以用数组 用数组记录每个随机指针的位置, 再去遍历链表。 这样做应该也是行的通的, 实现就不实现了。

所以,我们应该改变思路,不用传统的方法建表,同时,因为随机指针需要在复制过程中要有可以随机访问的特性,我们可以先生成一串零散的结点,保存起来,再访问原表,将这些结点按顺序串起来

那用什么数据结构呢? 哈希表。

使用哈希表可以用键值随机访问,如何拿到到我们需要的结点呢?我们想要的效果是:原表结点访问其随机指针时,新表也能快速拿到对应的结点。
那么,可以把原表的每个结点作为键,以新表的每个结点作为值,建立起映射。

这样,一切思路都通了。


在看代码之前,花点时间自己实践一些,一定有些细节还是需要思考的:


class Solution {
    public Node copyRandomList(Node head) {
        if(head==null ) return null ;
        HashMap<Node,Node> map =new HashMap<Node,Node> () ;
        Node cur = head;
        while(cur!=null) 
        {
            map.put(cur,new Node(cur.val)) ;
            cur=cur.next ;
        }
        cur =head ;
        while(cur!=null){
            map.get(cur).next = map.get(cur.next) ; 
            map.get(cur).random =map.get(cur.random) ;
        }
        return map.get(head) ;
    }
}

这个思路对我来讲还是很棒的,但面试官不满意! 有没有原地操作,空间复杂度为O(1)的? 有! 继续:…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值