Leetcode 代码随想录——刷题笔记(Ⅰ)

目录

数组

2024.3.4

704 二分法

27 移除元素

977 有序数组的平方

2024.3.5

209 最小长度的子数组

59  螺旋矩阵Ⅱ

总结

链表

2024.3.6

203 移除链表元素

2024.3.8

※707  设计链表(不太会)

206 反转链表(双指针法,递归法)

 2024.3.10

24 两两交换链表中的节点

2024.3.11

19 删除链表的倒数第N个节点

面试题 02.07 两两相交

142 环形链表 Ⅱ

哈希表

242 有效的字母异位词

2024.3.12

349 两个数组的交集

202 快乐数

1 两数之和

2024.3.13

454 四数相加Ⅱ

383 赎金信

 15 三数之和-双指针法

18 四数之和 -双指针法

字符串

2024.3.14

344 反转字符串

541 反转字符串Ⅱ

2024.3.15

※151 反转字符串里的单词

※KMP算法

※459 重复的子字符串

2024.3.16

※28 找出字符串中第一个匹配项的下标

双指针(前面做了)

2024.3.17 

27 移除元素

344 反转字符串

151 反转字符串里的单词

206 反转链表

19 删除链表的倒数第N个节点

面试题 02.07 两两相交

142 环形链表 Ⅱ

15 三数之和

18 四数之和


length属性→数组;length()方法→字符串String;size()方法→泛型集合

数组

2024.3.4


704 二分法

一般是左闭右闭和左闭右开

左闭右闭时,一般是left<=right ; right=middle-1; left=middle+1

左闭右开时,一般是left<right ; right=middle ; left=middle+1

mid = left + ((right - left) >> 1)的目的是为了计算leftright之间的中间索引,而不必担心整数溢出的问题(如果使用(left + right) / 2,当leftright都很大时可能会遇到溢出的问题)

  • 暴力解法时间复杂度:O(n)
  • 二分法时间复杂度:O(logn)
27 移除元素

双指针:快慢指针,双向指针

快慢指针:fast!=val时,将fast的值给slow,否则将fast+1然后再继续

双向指针:left和right,如果left的值为val,就将right的值赋给当前left,然后将right--,即删去这个值(即互换),然后再left++,当left和right指向同一个时,先判断再结束。

  • 暴力解法时间复杂度:O(n^2)
  • 双指针时间复杂度:O(n)
977 有序数组的平方

双指针复杂度为O(n),i=0,j为最后一个,然后比较两个数的平方,谁就放在新数组的后面,新数组下标减一,i++或者j--,再次比较(i<=j)

2024.3.5


209 最小长度的子数组

滑动窗口:用j表示终止位置,i初始位置为0,sum为总和,当sum>=target时,才将sum减去i所对应的值,并将i向后移一个,直到小于为止,因此用while,每次比较长度的大小取最小值;

在这里,将长度设置为int类型的最大值;如果sum直到j为最后一个值都没有大于等于target,说明本身数组总和就不大于等于这个数,不会进行比较取最小长度,那么长度的值仍为一开始的最大值

result = Integer.MAX_VALUE;

所以最后要加上一个判断,当仍为最大值时即不存在,返回0

 return result == Integer.MAX_VALUE ? 0 : result;
  • 暴力解法时间复杂度:O(n^2)
  • 滑动窗口时间复杂度:O(n)
59  螺旋矩阵Ⅱ

循环不变量对每条边的处理规则一样     左闭右开

用startx和starty记录每次循环的开始位置,初始值为0;用offset=1记录循环几圈,每一圈有四个循环,满足offset<=n/2时,进行循环填数,count初值为1,每次填数之后++。每一次循环之后,初始位置都要++,因为变成了里面一圈的位置开始,offset++进行下一圈循环。

如果n为奇数,那么循环完之后会有一个最中间的数,此时startx和starty表示的位置就是最后一个数的位置。

总结

来自 代码随想录知识星球 (opens new window)成员:海螺人的总结

链表

单链表,双链表,循环链表

2024.3.6


203 移除链表元素

要先判断要移除的元素是不是头结点,就得用两种方法移除;(持续移除的过程,为while )

可以使用虚拟头结点 dummy head,这样删除的结点不管是不是头结点都一样的操作

首先要判断链表是否为空!!!

        if(head==null){

            return head;

        }

定义虚拟结点   ListNode dummyhead=new ListNode(-1,head);值为-1,指针指向头结点

用.val表示值,.next表示指针指向的位置

2024.3.8


※707  设计链表(不太会)
//单链表
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }

要注意可以用for也可以用while,但是每次添加和删除节点时,已知的index要为遍历时cur.next,这样才能找到前一个节点进行连接。

206 反转链表(双指针法,递归法)

双指针:pre和cur,pre初始为虚拟,cur初始为head,结束的时候cur为null,pre为最后一个节点时就不用反转了,最后返回pre;每次先把cur.next保存下来,然后反转一个指针,将pre和cur向后移,先移动pre再移动cur,这样可以找到

while(cur!=null){

            temp=cur.next;

            cur.next=pre;

            pre=cur;

            cur=temp;

        }

 递归法:还是利用双指针,只不过是将函数进行递归使用

在主函数中直接赋值返回即可。return reverse(null,head);

private ListNode reverse(ListNode pre, ListNode cur){

        if(cur==null){

            return pre;

        }

        ListNode temp=cur.next;

        cur.next=pre;

        return reverse(cur,temp);   递归的关键地方

    }

 2024.3.10


24 两两交换链表中的节点

首先要判断结束标志:长度为0时,cur.next=null; 长度为偶数时,cur.next=null; 长度为寄数时,cur.next.next=null

 while(cur.next!=null && cur.next.next!=null)    不能写反,若cur.next=null; 写反了就会报错!

2024.3.11


19 删除链表的倒数第N个节点

快慢指针,先让快指针走n,之后快慢指针一起走,当快指向null时,慢指向想要删除的节点,但是我们要删除这个节点,就得知道前一个节点;因此,让快先走n+1即可。在while(n!=0 && fast!=null)之前,将n+1;while中每次n--

面试题 02.07 两两相交

先把两个链表长度计算出来,然后将长的链表附给A,找到两者长度差值gap,让长的链表的cur节点移动gap,之后判断两个链表的cur节点是否相等,不相等往后移动,相等返回当前节点。

142 环形链表 Ⅱ

如何判断是否有环快慢指针是否相遇;快走2 ,慢走1

有环时找到环的入口:从头节点开始的指针和相遇点开始的指针相遇的位置就是环入口

注:因为快每次比慢多一个,所以相遇的时候肯定慢在第一圈就可以相遇

判断条件 while(fast!=null && fast.next!=null)

哈希表

想知道一个元素是否出现过

三种数据结构:数组,set,map

范围可控,数比较小的情况下用数组;数值很大,用set;key:value用map

242 有效的字母异位词

就是两个字符串中出现的字母以及次数相同,不考虑顺序;

用哈希数组,字母有26个,数组的长度就为26,每个位置代表一个字母在字符串中出现的次数;第一个字符串中hash++,第二个字符串中hash--;如果最后hash中有不为0的,说明有字母个数不一样,则返回fasle;都为0,返回true。

2024.3.12

在 Java 中,不用一开始规定长度,可以用 add 方法动态添加元素的集合类包括 ArrayListHashSetLinkedListTreeSetLinkedHashSet 等。


349 两个数组的交集

使用数组来做哈希的题目,是因为题目都限制了数值的大小。而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。

1. 哈希集合HashSet底层为哈希表,不允许重复

Set<Integer> set1=new HashSet<>();
Set<Integer> resSet = new HashSet<>();

遍历添加的时候,可以普通的for循环

for(int j=0;j<nums2.length;j++){

           if(set1.contains(nums2[j])){

               result.add(nums2[j]);

           }

或者 将数组nums1中的元素逐个添加到名为set1的集合中;元素i,‘for-each’语句

for (int i : nums1) {
set1.add(i);
}

 转换为数组

return result.stream().mapToInt(x -> x).toArray();

2. 因为leetcode中又规定了数组的程度最大为1000,因此可以用哈希数组来做

int[] Hash1=new int[1002];
int[] Hash2=new int[1002];
List<Integer> resList = new ArrayList<>();

要将存放结果的列表转换为数组,并将所有值输出:

int index=0;
int[] res=new int[resList.size()];
for(int i:resList){
   res[index++]=i;
}
202 快乐数

哈希集合完成,首先写一个函数计算出每次平方和更新之后的数,用哈希集合将每次的数记录下来,数为1时停止,说明是快乐数;当哈希集合中有该数时,则说明又一次进入了数的循环,不可能到1;若最后n为1,则为快乐数。

快慢指针:相当于判断链表是否有环;当n为快乐数时,无环,最终快比慢先到达1;不是快乐数,那么就会循环,快和慢会在某一点相遇。快每次走两个,getNext()的嵌套。

1 两数之和

HashMap:需要知道一个元素是否出现过,并返回元素的下标,因此将元素作为key,value存储下标;map存储遍历过的元素,遍历当前元素时,看看map中有没有要找的元素,如果有,则返回这两个的下标。如果没有,把这个元素和下标存储在map中

int[] res=new int[2]用这个数组输出结果

Map<Integer,Integer> map=new HashMap<>();

map.containsKey(temp)  是否包含这个元素;map.get(temp)  获得value值;map.put(nums[i],i)  添加元素

2024.3.13


454 四数相加Ⅱ

分成两两的和进行寻找,不仅要找出和为0,还要记录有多少个元组可以满足,因此用map,key放和,value放出现的次数。

先遍历A+B,将其值和出现的次数记录在map中,然后遍历C+D,在map中寻找0-(C+D),如果有,则将count+其value的值,因为统计的是符合条件的个数

map.put(num, map.getOrDefault(num, 0) + 1)    用来统计num出现的次数;  map.getOrDefault(num, 0)    如果num在里面,则返回value,如果不在里面就返回0

383 赎金信

和有效的字母异位词差不多,hash数组记录字符串中字符出现的次数,另一个字符串中出现一次减去1,最后看看什么符合条件

要先把字符串转换为字符数组: for(char i:ransomNote.toCharArray()) 才能一个个遍历

 15 三数之和-双指针法

去重,因为要找的是元素的三元组,可能不同位置上三个元素相加为0【(-2,1,1)和(1,-2,1)】,但是三个元素的值是一样的,这时候只能算一个,但是哈希找这个数的时候是没有顺序的

双指针:对数组进行排序(从小到大)Arrays.sort(nums),遍历i,左边为left,右边为right,判断三者之和是大于0还是小于0;对两个指针进行适当的移动,但是也要注意去重。要先获取一个之后再去重

注:数组变量的嵌套写法  List<List<Integer>> res=new ArrayList<>();可以在里面放入list;这些list再组成list;   

  1.  对于每遍历一个i,判断当前最小数是否小于0,大于0就不可能有和为0的;
  2. 对a去重,要与前一个比较,若值一样,直接跳过该值
  3. 定义left和right,如果left<right就判断三数之和与0的关系,大于0将right--,小于0将left++;等于0的时候,将三个数加入到结果中res.add(Arrays.asList(nums[i], nums[left], nums[right])),并继续对b,c进行去重,如果left和后一个一样,就后移,right与前一个一样就前移;若没有应将left,right移动,进行下一次判断;
18 四数之和 -双指针法

剪枝:现在不能排序之后判断第一个数是不是大于target了,因为target可能是负数,负数相加会变小;当nums[i] > target && nums[i] > 0 && target>0三个条件都满足时,后面的数是越来越大的,相加肯定超过target,因此可以直接返回res或者break;二次剪枝可省略

注:因为第一次剪枝时,是在一个循环中,不符合条件可以直接返回res;但是在第二次剪枝的时候,判断的是nums[i]+num[j],如果大于target,说明j之后的数都大,如果直接res,相当于后面的情况都不考虑了,但是,有可能i和j中间有很多数,其他数可以相加等于target,能和nums[i]相加的数已经在这之前遍历过了,因此需要break跳出循环,遍历i++的情况。

nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出

long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];

字符串

2024.3.14


344 反转字符串

用两个指针在左右两边进行交换就行,结束条件就是l>=r;或者l<s.length/2;定义的中间变量temp为char类型

541 反转字符串Ⅱ

题中给的是字符串,首先要变为字符数字才能进行反转, char[] ch=s.toCharArray();对于每次循环i间隔2k,这样在每次循环中对前k个进行反转就可;结束条件是i<ch.length;

在一次循环里(最后一次)时,确定end,要判断反转结束位置(start+k-1)与ch的最后一个位置的大小(ch.length-1),使用Math.min(),反转结束位置小说明剩下的可能大于k小于2k;最后一个位置小,说明剩下的不够k,对这些进行反转。最后的返回要变成字符串形式: return new String(ch);

2024.3.15

151 反转字符串里的单词

不使用创建新的数组,在原数组上进行改动,空间复杂度为O(1);使用双指针,时间复杂度为O(n)

思路先用快慢指针移除多余的空格;再整体反转字符串;再每个单词进行翻转

charAt()方法用于返回指定索引处的字符值,而字符值是不可变的;所以 s.charAt(slow) = ' ' 就是错误的,只能用StringBuilder是可变的字符串类,可以方便地进行字符位置的修改和操作。

循环中,如果快指针指向空就向后移动,慢指针不动;若快指针不为空,要判断slow是不是指向0,指向0说明在开头,那么就将fast指向的值加入到创建的对象sb中,直到fast再次为空,说明一个单词结束;然后fast继续向后移动,再次不为空的时候判断slow是不是在0的位置,很显然肯定不是0说明不在开头单词,那么这时候就要给单词之间加上空格,给sb加入一个空格,并将slow向后移动,就会继续将fast的值给slow(即将值插入到sb中)直到这个单词结束,以此往复。当fast到最后位置的时候也会结束。

注:while里面是一个单词中所有字符的判断,每加入一个字符就将两个指针后移一位,当一个单词结束,fast指向空就会结束while循环,进行for循环里面的fast++;直到fast再次不为空,进行if里面slow的判断,由此判断是不是第一个单词,不是第一个单词就加入空格并将slow++

 public StringBuilder removeSpace(String s){
        StringBuilder sb = new StringBuilder();
        //定义快慢指针
        int slow=0;
        for(int fast=0;fast<s.length();fast++){
            if(s.charAt(fast)!=' '){
                //保留单词间的空格
                if(slow!=0){
                   sb.append(' ');
                    slow++;
                }
                while(fast<s.length() && s.charAt(fast)!=' '){
                    sb.append(s.charAt(fast));
                    slow++;
                    fast++;
                }
            }
        }
        return sb;
    }

第二步就是对整个字符串进行反转,sb.setCharAt(0,c) 是将值c赋给0的位置

 public void reverseString(StringBuilder sb, int start, int end) {
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        } 
    }

之后对每个单词进行反转,调用 reverseString()方法,先找到每个单词的起始位置和结束位置,初始让end=1,如果end不为空,就后移,直到为空,这时将这个单词反转;之后下一个单词的开始位置为end+1,结束位置向后一个,并重新开始找到新的单词的结束位置;直到开始位置或者结束位置到最后的时候结束。

private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
            reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }

最后输出要转化 return sb.toString(); 

KMP算法

前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。

后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。

找一个字符串是否在主串中出现过

最长相等前后缀;例:aabaaf,前缀表就为010120,数字代表这之前子串的最长相等前后缀长度

每次匹配失败的位置(失败了就找去掉这个位置的子串的最长相等前后缀长度也就是next的前一个值,看看匹配的长度是多少,下次直接从哪里开始匹配,虽然next里面的值是长度,假如值为2,也就是跳到下标为2的位置,相当于从第三个数开始比较,前两个数是匹配的),即找到前一位的前缀表值,直接回到下一个继续匹配的位置。next数组里面就是前缀表里的值,代表前缀的长度,但是有的是统一减一(右移一位,初始位置为-1);如果0开头就是原始的。

找模式串的前缀表:1.先初始化,i表示后缀末尾,j表示前缀末尾位置,j也代表着包括i之前的子串的最长相等前后缀的长度。  2.前后缀不相同 3.前后缀相同 4. 更新next

j=0;

next[0]=0;

for(int i=1;i<s.size;i++){

        while(j>0 && s[i]!=s[j]){      #不相等时说明前后缀不匹配,j看前一个位置对应的数退回

                j=next[j-1];

        }

        if(s[i]==s[j])  j++;              #相等说明最长相等前后缀的值j+1

        next[i]=j;

}

459 重复的子字符串

 如果一个字符串是由重复子串组成的,那么他的最小重复单位就是他的最长相等前后缀不包含的那一部分(自己可以去推导);因为前后缀都是不包括开头和结尾的,所以剩下的必定是一个重复单元

1. 移动匹配:字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。如s=abcabc里面由两个一样的abc组成,s+s中,剔除首和尾,就一定还会出现s

2. KMP: 数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。

if (s.equals("")) return false;

        int len = s.length();
        // 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
        s = " " + s;
        char[] chars = s.toCharArray();
        int[] next = new int[len + 1];

        // 构造 next 数组过程,j从0开始(空格),i从2开始
        for (int i = 2, j = 0; i <= len; i++) {
            // 匹配不成功,j回到前一位置 next 数组所对应的值
            while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
            // 匹配成功,j往后移
            if (chars[i] == chars[j + 1]) j++;
            // 更新 next 数组的值
            next[i] = j;
        }

        // 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
        if (next[len] > 0 && len % (len - next[len]) == 0) {
            return true;
        }
        return false;

代码中next中相当于将所有值向后移,每个字符对应位置上的next的值就是以该字符结尾的子串的最长相等前后缀大小。[  ,  ,0,0,1,2,3,4,5,6]

2024.3.16


※28 找出字符串中第一个匹配项的下标

先算出模式串的next数组

然后定义两个指针对应模式串j和主串i的下标,均从0开始,i循环,对于每一个i小于主串的长度,如果模式串中的j对应的数相等,那么j++,i++同时向后移动开始下一次循环,如果不相等,就将j回退到上次前后缀相等的位置看是否相等,直到相等或者j=0;循环中每次判断j和模式串的长度是否相等,因为j代表的就是主串中与模式串相匹配的长度,如果相等了说明在主串中出现了,就返回第一个匹配项的下标 i-len+1(因为还在这次的循环中,i指的是匹配的最后一个位置的下标)

int n = haystack.length(), m = needle.length();
        if (m == 0) {
            return 0;
        }
        int[] pi = new int[m];
        for (int i = 1, j = 0; i < m; i++) {
            while (j > 0 && needle.charAt(i) != needle.charAt(j)) {
                j = pi[j - 1];
            }
            if (needle.charAt(i) == needle.charAt(j)) {
                j++;
            }
            pi[i] = j;
        }
        for (int i = 0, j = 0; i < n; i++) {
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                j = pi[j - 1];
            }
            if (haystack.charAt(i) == needle.charAt(j)) {
                j++;
            }
            if (j == m) {
                return i - m + 1;
            }
        }
        return -1;

双指针(前面做了)

2024.3.17 


数组,字符串,链表,多数之和(去重) 

27 移除元素
344 反转字符串
151 反转字符串里的单词
206 反转链表
19 删除链表的倒数第N个节点
面试题 02.07 两两相交
142 环形链表 Ⅱ
15 三数之和
18 四数之和
  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值