Leetcode刷题记录

本文记录了LeetCode中的一些经典算法题目的解题思路,包括两数相加、最长回文串、最小的k个数、水壶问题、整数反转、链表中间节点、反转链表等。通过数学方法、动态规划、贪心思想和搜索算法等多种解题策略进行解析,适合程序员提升算法能力。
摘要由CSDN通过智能技术生成

02. 两数相加

两数相加
伪代码如下:

  • 将当前结点初始化为返回列表的哑结点。
  • 将进位 carry初始化为 0。
  • 将 p 和 q分别初始化为列表 l1和 l2的头部。
  • 遍历列表 l1 和 l2 直至到达它们的尾端。
    • 将 x 设为结点 p 的值。如果 p 已经到达 l1 的末尾,则将其值设置为 0。
    • 将 y 设为结点 q 的值。如果 q 已经到达 l2 的末尾,则将其值设置为 0。
    • 设定 sum = x + y + carry。
    • 更新进位的值,carry = sum / 10。
    • 创建一个数值为 (sum \bmod 10) 的新结点,并将其设置为当前结点的下一个结点,然后将当前结点前进到下一个结点。
    • 同时,将 p 和 q 前进到下一个结点。
  • 检查 carry = 1是否成立,如果成立,则向返回列表追加一个含有数字 1 的新结点。
  • 返回哑结点的下一个结点。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode dummyHead = new ListNode(0);
    ListNode p = l1, q = l2, curr = dummyHead;
    int carry = 0;
    while (p != null || q != null) {
        int x = (p != null) ? p.val : 0;
        int y = (q != null) ? q.val : 0;
        int sum = carry + x + y;
        carry = sum / 10;
        curr.next = new ListNode(sum % 10);
        curr = curr.next;
        if (p != null) p = p.next;
        if (q != null) q = q.next;
    }
    if (carry > 0) {
        curr.next = new ListNode(carry);
    }
    return dummyHead.next;
}

409. 最长回文串

最长回文串
友情提示:遇到有提示字符串仅包含小写(或者大写)英文字母的题,都可以试着考虑能不能构造长度为26的每个元素分别代表一个字母的数组,来简化计算。

思路:用长度为58的数组来保存每个字母出现的次数。如果一个字母存在偶数次,那么一定可以用于构造回文;如果一个字母出现奇数次,可以放在中间,也可以取奇数次-1次(即偶数个)该字母用于放在两边构造回文。
因此:

  • 如果存在奇数次的字母,最长回文数=(所有偶数次的和)+(所有(奇数次-1)的和)+1;
  • 如果不存在奇数次的字母,最长回文数 = 所有偶数次的和;
import java.util.HashMap;
import java.util.Map;
class Solution {
    public int longestPalindrome(String s) {
        if (s.length()==0){
            return 0;
        }
       int[] count = new int[58];
        char[] c = s.toCharArray();
        for(char cc:c){
            count[(int)cc-'A']+=1;
        }
        int sum = 0;
        boolean flag =false; //来表示有没有奇数项
        for(int i:count){
            // i是偶数
            if(i%2==0){
                sum += i;
            }else{
                flag = true;
                sum += i-1;
            }
        }
        if(flag){
            return sum+1;
        }
        return sum;
    }
}

40. 最小的k个数(剑指offer40题)

最小的k个数
网易面试考过找最小的2个数,建个长度为2的数组,遍历1遍就可以找了。
但是这个找最小的k个数,所以我的第一直觉是先对原数组从小到大排序,然后返回前k个数。

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quicksort(arr,0,arr.length-1);
        int[] re = new int[k];
        for(int i = 0; i<k;i++){
            re[i] = arr[i];
        }
        return re;
        
    }
    private void quicksort(int[] arr,int left,int right){
        if(left>right){
            return;
        }
        int base = arr[left];
        int i = left;
        int j = right;
        while(i!=j){
            while(arr[j]>=base && j>i){
                j--;
            }
            while(arr[i]<=base && j>i){
                i++;
            }
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        arr[left] = arr[i];
        arr[i] = base;
        quicksort(arr,left,i-1);
        quicksort(arr,i+1,right);
    }
}

365. 水壶问题

水壶问题
主要思路:求解ax+by=z这个方程存不存在整数解a,b;

解法1:(数学方法)

根据祖定理可知,判断该线性方程是否有解需要需要判断z是否为x,y最大公约数的倍数。此时为题转化为了求解最大公约数,而该问题可以使用gcd算法(辗转相除法)
作者:wang-code-2
链接:https://leetcode-cn.com/problems/water-and-jug-problem/solution/shui-hu-wen-ti-de-jie-ti-si-kao-java-by-wang-code-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

参考代码如下:(我当时不会实现辗转相除法。。所以直接复制了这位大佬的代码)

class Solution {
    public boolean canMeasureWater(int x, int y, int z) {
        if(x==z||y==z||x+y<=z){
            if(x+y<z){
                return false;
            }
            return true;
        }
        return x>y?(z%gcd(x,y))==0:(z%gcd(y,x))==0;
    }
    public int gcd(int x,int y){
        return y==0?x:gcd(y,x%y);
    } 
}
作者:wang-code-2
链接:https://leetcode-cn.com/problems/water-and-jug-problem/solution/shui-hu-wen-ti-de-jie-ti-si-kao-java-by-wang-code-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解法2:图的广度优先搜索(BFS)

没看懂其实。。先马住大佬的解法。

力扣大佬的BFS解法

这一类游戏相关的问题,用人脑去想,是很难穷尽所有的可能情况的。因此很多时候需要用到「搜索算法」。
「搜索算法」一般情况下是在「树」或者「图」结构上的「深度优先遍历」或者「广度优先遍历」。因此,在脑子里,更建议动手在纸上画出问题抽象出来的「树」或者「图」的样子。
在「树」上的「深度优先遍历」就是「回溯算法」,在「图」上的「深度优先遍历」是「flood fill」 算法,深搜比较节约空间。这道题由于就是要找到一个符合题意的状态,我们用广搜就好了。这是因为广搜有个性质,一层一层像水波纹一样扩散,路径最短。
作者:liweiwei1419
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

7. 整数反转

不考虑特殊情况时:

  • 输入x,输出ans
  • 当x!=0时,ans = ans*10 + x%10; x /= 10;

主要是如何处理边界值Integer.MAX_VALUE和Integer.MIN_VALUE的反转问题。
要判断每次拼接之后的ans有没有超过最大值和最小值。
(中间我用ans*10>Integer.MAX_VALUE这样比错了,因为ans*10超过最大值的时候比不出来。。so,应该ans>Integer.MAX_VALUE/10这样比)
另外,还要注意当ans==Integer.MAX_VALUE/10时,因为[−2^31, 2^31 − 1], 即:[-2147483648, 2147483647],所以要判断一下当前取余的temp2是否>7。负数同理。

class Solution {
    public int reverse(int x) {
        int temp1=x,temp2=0,ans=0;
        while(temp1!=0){
            temp2 = temp1%10;
            if(ans>Integer.MAX_VALUE/10 || (ans==Integer.MAX_VALUE/10 && temp2>7)){
                return 0;
            }
            if(ans<Integer.MIN_VALUE/10 || (ans==Integer.MIN_VALUE/10 && temp2<-8)){
                return 0;
            }
            ans = ans*10 +temp2;
            temp1 = temp1/10;
        }
        return ans;
    }
}

876. 链表的中间节点

链表的中间节点(简单)
(快慢指针)用两个指针变量遍历单链表,一个一次挪动一个节点(L1),一个一次挪动两个节点(L2)。

  • 链表长度为奇数:当L2=null时,L1所指的节点就是中间节点。
  • 链表长度为偶数时,当L2.next=null时(即L2是尾节点),L1所指的节点就是中间节点。
public ListNode middleNode(ListNode head) {
        ListNode L1 = head;
        ListNode L2 = head;
        while(L2!=null && L2.next != null){
            L1 = L1.next;
            L2 = L2.next.next;
        }
        return L1;
    }

24. 反转链表

面试问过。
用双指针迭代,一个pre指向前一个节点,cur指向当前节点,然后循环的中间用一个临时指针temp来指当前节点的下一个节点,以便完成双指针的迭代。

public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        ListNode temp = null;
        while(cur!=null){
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

解法二:递归解法
LeetCode大佬的多种解法
想是没想到,看解析的动画看懂了,马一下。

面试题14 剪绳子

剪绳子1

剪绳子

  • 动态规划(时间复杂度O(n^2),空间复杂度O(n))
  • 贪心思想(时间复杂度O(1),空间复杂度O(1))
动态规划

递归表达式dp[i] = Math.max(dp[j]*dp[i-j], dp[i])

public class Solution {
    public int cutRope(int n) {
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        int[] dp = new int[n + 1];
        dp[2] = 2;
        dp[3] = 3;
        for (int i = 4; i <= n/2; i++) {
            for (int j = 1; j < i; j++) {
                dp[i] = Math.max(dp[j]*dp[i-j], dp[i]);
            }
        }
        return dp[n];
    }
}
贪心思想

图解
(来自上面图解链接里的描述)贪心规则
最高优先级: 3 。把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0,1,2三种情况。
次高优先级: 2 。若最后一段绳子长度为 2,则保留,不再拆为 1+1 。
最低优先级: 1; 若最后一段绳子长度为 1;则应把最后的 3 + 1替换为 2 + 2,因为2×2>3×1。
算法流程:
当n≤3 时,按照贪心规则应直接保留原数字,但由于题目要求必须剪成 m>1段,因此必须剪出一段长度为 1 的绳子,即返回 n - 1。
当 n>3时,求n除以 3的 整数部分 a和 余数部分 b (即 n = 3a + b),并分为以下三种情况:
当 b = 0时,直接返回 3^a;
当 b = 1时,要将一个 1 + 3转换为 2+2,因此返回 3^{a-1} ×4;
当 b = 2时,返回 3^a×2。

剪绳子2(取模)

在1的基础上,答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

  • 知识补充:大数问题的取模问题。大数越界情况下的求余问题(基于上面贪心思想的解法继续讨论)

    大数越界:当a增大时,最后返回的3^a大小以指数级别增长,可能超出int32甚至int64的取值范围,导致返回值错误。
    本题的大数求余问题: 在仅使用 int32 类型存储的前提下,正确计算 (x^a)对 pp 求余(即 x^a ⊙p )的值。
    解决方案: 循环求余 、 二分求余 ,其中后者的时间复杂度更低,两种方法均基于以下求余运算规则推出:(xy)⊙p=[(x⊙p)(y⊙p)]⊙p。

每次运算的时候都取个模就不会出现溢出的情况了。

class Solution {
    public int cuttingRope(int n) {
        if(n == 2) {return 1;}
        if(n == 3) {return 2;}
        int mod = (int)1e9 + 7;
        long res = 1;
        while(n > 4) {
            res *= 3;
            res %= mod;
            n -= 3;
        }
        return (int)(res * n % mod);
    }
}

48.最长不含重复字符串

LeetCode
动态规划
用f(i)表示以第i个字符结尾的最长不重复字符串的长度,d表示第i个字符和上个相同字符间的距离。
f(i)=f(i-1)+1, when d<=f(i-1)
f(i)=f(i-1)+1, when前面无重复字符或d>f(i-1)
改进了一下,用HashMap存一下已出现过的字符,然后不用f(n)这个数组了,直接用pre表示f(i-1),空间复杂度还是O(N);时间复杂度O(N)(哈希表里查找是O(1))
每次查找到重复的字符的时候需要在哈希表里更新一下最新出现的位置。

public static int lengthOfLongestSubstring(String s) {
        if(s.length()==0){
            return 0;
        }
        int pre = 0;
        HashMap<Character,Integer> map = new HashMap<>();
        int max = 1;
        for(int i=0;i<s.length();i++){
            char cur = s.charAt(i);
            int d = 0;
            if(map.containsKey(cur)){
                d = i-map.get(cur);
            }
            map.put(cur,i);
            if(d==0 || d>pre){
                pre = pre+1;
            }else{
                pre = d;
            }
            if(pre>max){
                max = pre;
            }
        }
        return max;
    }

另一个常用思路:
滑动窗口(双指针):定义head,tail两个指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值