返回顶部

【算法题】LeetCode刷题(五)

数据结构和算法是编程路上永远无法避开的两个核心知识点,本系列【算法题】旨在记录刷题过程中的一些心得体会,将会挑出LeetCode等最具代表性的题目进行解析,题解基本都来自于LeetCode官网(https://leetcode-cn.com/),本文是第五篇。

1.跳跃游戏(原第55题)

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。

示例:

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

(1)知识点

贪心算法

(2)解题方法

方法转自:https://leetcode-cn.com/problems/jump-game/solution/tiao-yue-you-xi-by-leetcode-solution/

方法:贪心算法

这里考虑用贪心算法解决:对于一个数组,我们在每个位置都有一个它可以到达的最远位置,拿示例来说,记录max为可以到达的最远位置,并且遍历的过程中实时更新,那么在i=0时,max=0+a[0]=2,i=1时,max=max(2,1+a[1])=4,...
这样只要其中更新max的时候发现max+1大于数组长度就可以了

  • 时间复杂度:O(n),其中 n 为数组的大小。只需要访问 nums 数组一遍,共 n 个位置。
  • 空间复杂度:O(1),不需要额外的空间开销。

(3)伪代码

函数头:boolean canJump(int[] nums)

方法:贪心算法

  • 定义最大位置maxPos=0
  • 第一重循环:i=0->len
    • maxPos=max(maxPos,nums[i]+i])
    • 如果maxPos>len-1,直接跳出返回true
  • 返回false

(4)代码示例

public boolean canJump(int[] nums) {
    int len = nums.length;
    if(len == 0 || len == 1) return true;
    int max = nums[0];
    for(int i = 1; i < len; ++i){
        if(max >= i){
            max = Math.max(max, i + nums[i]);
        }else{
            return false;
        }
    }
    return true;
}


2.旋转链表(原第61题)

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

(1)知识点

循环链表

(2)解题方法

方法转自:https://leetcode-cn.com/problems/rotate-list/solution/xuan-zhuan-lian-biao-by-leetcode/

方法:循环链表+双指针

当出现单链表的旋转问题,即位置平移的时候,可以考虑将单链表的首尾相连,然后从某个位置断开即可。
那么这就分成了两部分:第一步,构建循环链表,这里需要遍历一次链表,将最末尾的指向最开头。第二步,找到应该断开的位置,将该位置前一个指针指向null,然后返回该位置的指针,这里可以采用双指针的方式,在第一次循环的时候记录下对应位置的节点。

  • 时间复杂度:O(n),其中 N 是链表中的元素个数
  • 空间复杂度:O(1),不需要额外的空间开销。

(3)伪代码

函数头:ListNode rotateRight(ListNode head, int k)

方法:循环链表+双指针

  • 定义一个移动的节点move=head,定义一个要断开的节点tmp=head
  • 第一重循环:move.next!=null
    • move后移
    • k--,当k>0时,tmp后移
  • 将move和head连起来
  • 存储好tmp.next节点,再将tmp.next置空
  • 返回这个存好的节点

(4)代码示例

public ListNode rotateRight(ListNode head, int k) {
    if(head == null || head.next == null) return head;
    ListNode find = head;
    int n = 0;
    while(find != null){
        ++n;
        find = find.next;
    }
    k = k % n;
    if(k == 0) return head;
    
    ListNode fast = head;
    ListNode slow = head;
    while(fast.next != null){
        --k;
        fast = fast.next;
        if(k < 0){
            slow = slow.next;
        }
    }
    ListNode tmp = slow;
    slow = slow.next;
    tmp.next = null;
    fast.next = head;
    return slow;
}


3.最小路径和(原第64题)

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

示例:

输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

(1)知识点

循环链表

(2)解题方法

方法转自:https://leetcode-cn.com/problems/minimum-path-sum/solution/zui-xiao-lu-jing-he-by-leetcode-solution/

方法:动态规划

动态规划重要的是找到状态转移方程,那么对于这个题,首先我们很清楚的是:因为只能往右和往下跑,所以,第一排和第一列的数永远只有一种方式到达,然后从第二排第二列那个数开始,就有多种方式了,但是它的最短路径还是受限于它的左边那个数的最短路径和上边那个数的最短路径。以此类推,按照这个顺序一个一个填,很轻松就能填好一张最短路径表。

  • 时间复杂度:O(mn),其中 m 和 n 分别是网格的行数和列数。需要对整个网格遍历一次,计算 dp 的每个元素的值。
  • 空间复杂度:O(mn),其中 m 和 n 分别是网格的行数和列数。创建一个二维数组 dp,和网格大小相同。空间复杂度可以优化,例如每次只存储上一行的 dp 值,则可以将空间复杂度优化到 O(n)。

(3)伪代码

函数头:int minPathSum(int[][] grid)

方法:动态规划

  • 定义一个行列和grid相同的数组dp,用来存放最短路径表
  • 遍历grid的行,将dp的行填满
  • 遍历grid的列,将dp的列填满
  • 从i=1,j=1开始,遍历grid,填满dp
  • 最后返回dp右下角的数

(4)代码示例

public int minPathSum(int[][] grid) {
    int len = grid.length;
    if(len == 0) return 0;
    int wid = grid[0].length;
    int [][]dp = new int[len][wid];
    dp[0][0] = grid[0][0];
    for(int i = 1; i < len; ++i){
        dp[i][0] = dp[i-1][0] + grid[i][0];
    }
    for(int i = 1; i < wid; ++i){
        dp[0][i] = dp[0][i-1] + grid[0][i];
    }
    
    for(int i = 1; i < len; ++i){
        for(int j = 1; j < wid; ++j){
            dp[i][j] = Math.min(dp[i][j-1], dp[i-1][j]) + grid[i][j];
        }
    }
    return dp[len-1][wid-1];
}


4.加一(原第66题)

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。

示例:

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。

(1)知识点

进位

(2)解题方法

方法转自:https://leetcode-cn.com/problems/plus-one/solution/hua-jie-suan-fa-66-jia-yi-by-guanpengchn/

方法:加一

这个题看似简单,当然实际上也很简单,但是这么简单一个题很容易做复杂,其实只需要思考两种情况,一种个位不为9,那么万事大吉,但是代码就得考虑个位为9的情况,那么代码怎么写最精简这是最难的。一种常规思路是,先判断个位,为9个位加一直接输出,不为9再说,那么这种情况就复杂了,如果十位也为9呢?这样考虑代码势必越来越复杂。所以我们采用下面的思路:
见伪代码

(3)伪代码

函数头:int[] plusOne(int[] digits)

方法:加一

  • 第一重循环(i:len-1->0)
    • dights[i]++
    • x=dights[i]%=10
    • 如果x不为0,直接返回dights
  • digits = new int[len + 1];(这个地方要注意,到了这个位置dights所有位必全为0,直接new一个新数组就行了(默认是0))
  • digits[0] = 1;
  • 返回dights

(4)代码示例

public int[] plusOne(int[] digits) {
    int len = digits.length;
    int x = 1;
    for(int i = len-1; i >= 0; --i){
        digits[i] += x;
        x = digits[i] / 10;
        digits[i] %= 10;
    }
    if(x > 0){
        int[] newDig = new int[len+1];
        newDig[0] = x;
        for(int i = 0; i < len; ++i){
            newDig[i+1] = digits[i];
        }
        return newDig;
    }
    return digits;
}


5.x的平方根(原第69题)

实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。(不允许用sqrt函数)

示例:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。

(1)知识点

数学

(2)解题方法

方法转自:https://leetcode-cn.com/problems/sqrtx/solution/x-de-ping-fang-gen-by-leetcode-solution/

方法一:袖珍计算器算法

由于不允许使用sqrt函数,我们可以寻求一个替代的方法。可以看出x(1/2)=e((1/2)lnx)。

  • 时间复杂度:O(1),由于内置的 exp 函数与 log 函数一般都很快,我们在这里将其复杂度视为 O(1)。
  • 空间复杂度:O(1)。

方法二:二分查找(此处就是最基本的二分查找,不做赘述)

  • 时间复杂度:O(logx),即为二分查找需要的次数。
  • 空间复杂度:O(1)。

方法三:牛顿迭代

实话说,我想不到这个方法,但是还是记一下,以后被问到可以知道有这么回事。用我们以前学过的迭代方法,可以将问题转化为求y=x^2-C的零点,方法如下:

  • 时间复杂度:O(logx),此方法是二次收敛的,相较于二分查找更快。
  • 空间复杂度:O(1)。

(3)伪代码

函数头:int mySqrt(int x)

方法一:袖珍计算器算法

  • 计算y=exp(0.5*log(x))
  • 如果y+1的平方比x大,则返回y,否则返回y+1

方法二:二分查找(略)

方法三:牛顿迭代

  • 第一重循环(true)
    • double xi = 0.5 * (x0 + C / x0);
    • 如果xi和x0的差值小于10^(-7),直接返回xi的整数部分
    • x0=xi

(4)代码示例

public int mySqrt(int x) {
    int result = (int)Math.exp(0.5 * Math.log(x));
    return (long)(result + 1) * (result + 1) <= x ? result + 1 : result;
}


posted @ 2020-08-07 08:43  藤原豆腐店の張さん  阅读(398)  评论(0编辑  收藏  举报