java学习之路算法提高之LeetCode刷题算法

leetCode进阶算法题+解析(九)

2020-02-04  本文已影响0人  唯有努力不欺人丶

现在每天混低保,一天三道题很稳。

旋转链表

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

示例 1:
输入: 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
示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

思路:感觉这道题实现是很容易的,甚至超容易,我直接把链表存成数组,然后旋转弄能新数组,再然后一个个挂上。实现是能实现了,但是感觉太傻了吧。我不知道这个题是不是有啥隐含的要求,但是目前看来是没说不能用数组或者一次遍历啥的。我先去最笨的方式实现了再说。
就是用这种无脑的方式写出来了,然后两次ac了,甚至性能都如我所料,可怜的一批。但是毕竟无脑方便,我还是贴出来分享下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        ListNode cur = head;
        while(cur!=null){
            list.add(cur.val);
            cur = cur.next;
        }
        if(list.size()==0) return head;
        k = k%list.size();
        if(k==0) return head;
        while(k>0){
            list.addFirst(list.removeLast());
            k--;
        }
        ListNode res = new ListNode(0);
        ListNode temp = res;
        for(int i = 0;i<list.size();i++){
            temp.next = new ListNode(list.get(i));
            temp = temp.next;
        }
        return res.next;
    }
}
性能超过百分之九点八

说正经的这道题,我其实做过类似的,我记得好像是双指针,一个先走k个节点,然后两个指针同时往后走啥的,我先理理思路。
我直接贴代码:

public ListNode rotateRight(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        int length = 0;
        ListNode cur = head;
        ListNode tail = null;
        while (cur != null) {
            length++;
            if (cur.next == null) {
                tail = cur;
            }
            cur = cur.next;
        }
        tail.next = head;
        int n = k % length;
        for (int i = 0; i < length - n; i++) {
            tail = tail.next;
        }
        cur = tail.next;
        tail.next = null;
        return cur;
}

细品吧,感觉思路绕点,但是理解了代码不难。其实这个就是首尾相连,然后找断点。就酱吧,下一题。

不同路径

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?
题目截图

说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右
    示例 2:
    输入: m = 7, n = 3
    输出: 28

思路:审题完第一反应,这个绝对是dp。典型的动规题。至于要怎么写,还是要好好理理思路的,我去实现啦。
做完回来了,咱们继续说思路。如果对动规不太理解的建议去看看我有一篇专门讲动态规划的笔记。动态规划 ——用数组记录中间过程的递归
其实看名字就能明白,所谓动规,就是用数组记录中间过程。
这个题我反正做的时候是用二维数组来理解的。我直接上图吧(一如既往的渣,但是我用心了啊)其中注意点:

  1. 当只有一排和一列的情况下,肯定答案是1.因为只有一种走法。
  2. n = 2 ,m=3 和n = 3,m=2是一个性质,次数是相同的。所以其实这个二维数组还勉强可以用中间对称来看。
  3. 咱们从左上角开始算,左边的块再走一步到右边这块,上面的块再走一步到下边这块。也就是当一个块不是挨着边的,那么不同路径数就是左边的路径数加上上边的路径数。
图解

(如图表示,我没画完,反正大概就是这个意思。)然后只要看懂这个图就能直接写出代码了,反正我是一个个填进来的。我直接贴代码:

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0)
                    dp[i][j] = 1;
                else {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }
        return dp[m - 1][n - 1]; 
    }
}

然后这道题就这么过了,下一题我看了是不同路径2.不知道是怎么在这个基础上的变种。

不同路径2

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
题目截图

示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右

思路:其实这个题数组还是那个数组,只不过区别就是当遇到1,也就是障碍物时。这条路就不通了。比如这个1在目标的上面,那么到目标的路径数就不是上+左。而只有左。同理在左边,就是只有上。当然如果障碍物在目标 的右下方就一点影响没有,反正二维数组还是这个数组。只不过在填充这个数组的时候做点处理而已。我去实现了。
好了,实现是实现了,本以为是手到擒来,但是还是不断在测试案例的教训下不断修改。我直接贴代码:

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        //因为可能有次数是1的,我想在这个二维数组基础上操作,所以把1换成-1
        for(int i = 0;i<obstacleGrid.length;i++){
            for(int j=0;j<obstacleGrid[0].length;j++){
                if(obstacleGrid[i][j]==1) obstacleGrid[i][j] = -1;
            }
        }
        for(int i = 0;i<obstacleGrid.length;i++){
            for(int j=0;j<obstacleGrid[0].length;j++){
                if(i == 0 || j == 0){
                    if(i == j){
                        if(obstacleGrid[i][j] == -1){
                            return 0;
                        }else{
                            obstacleGrid[i][j] = 1;
                        }
                         
                    }else if(obstacleGrid[i][j] == -1){
                        continue;
                    }else if(i == 0 && obstacleGrid[i][j-1] == -1){
                        obstacleGrid[i][j] = -1;
                    }else if(j == 0 && obstacleGrid[i-1][j] == -1){
                        obstacleGrid[i][j] = -1;
                    }else{
                        obstacleGrid[i][j] = 1;
                    }
                }else if(obstacleGrid[i][j] != -1){
                    int sum = 0;
                    if(obstacleGrid[i][j-1] != -1) sum += obstacleGrid[i][j-1];
                    if(obstacleGrid[i-1][j] != -1) sum += obstacleGrid[i-1][j];
                    obstacleGrid[i][j] = sum;
                }
            }
        }
        int res = obstacleGrid[obstacleGrid.length-1][obstacleGrid[0].length-1];
        return res==-1?0:res;
    }
}

首先这个边边的1 是不能直接写了,因为如果有障碍物以后的就不能写了。其次如果终点是障碍物则直接0.反正不难,就是测试案例真的考虑情况很多,一点点微调才做完的.....
好了,稍微改良版出来了:

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int len = obstacleGrid.length;
        if(len==0) return 0;
        int row = obstacleGrid[0].length;
        if(row ==0) return 0;
        //入口出口有障碍物直接0
        if(obstacleGrid[0][0]==1 || obstacleGrid[len-1][row-1]==1) return 0;
        //因为可能有次数是1的,我想在这个二维数组基础上操作,所以把1换成-1
        for(int i = 0;i<len;i++){
            for(int j=0;j<row;j++){
                if(obstacleGrid[i][j]==1) obstacleGrid[i][j] = -1;
            }
        }
        for(int i = 0;i<len;i++){
            for(int j=0;j<row;j++){
                if(i == 0 || j == 0){
                    if(i == j){
                         obstacleGrid[i][j] = 1;    
                    }else if(obstacleGrid[i][j] == -1){
                        continue;
                    }else if(i == 0){
                        obstacleGrid[i][j] = obstacleGrid[i][j-1];
                    }else if(j == 0){
                        obstacleGrid[i][j] = obstacleGrid[i-1][j];
                    }
                }else if(obstacleGrid[i][j] != -1){
                    int sum = 0;
                    if(obstacleGrid[i][j-1] != -1) sum += obstacleGrid[i][j-1];
                    if(obstacleGrid[i-1][j] != -1) sum += obstacleGrid[i-1][j];
                    obstacleGrid[i][j] = sum;
                }
            }
        }
        return obstacleGrid[len-1][row-1];
    }
}

反正这道题就这样了,然后就是多种情况考虑吧.这道题过.

最小路径和

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

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

思路:这几个动态规划的题又凑到一块了.反正就是典型动规.然后知道一个点的上面最小路径和左边最小路径.然后取最小的加上当前值就行了.感觉这个题不需要画图了,和上面的差不多,我直接撸代码吧.
好了,这道题并没有什么坑,一次过了,我直接贴代码:

class Solution {
    public int minPathSum(int[][] grid) {
        int len = grid.length;
        if(len == 0) return 0;
        int row = grid[0].length;
        if(row == 0) return 0;
        for(int i = 0;i<len;i++){
            for(int j = 0;j<row;j++){
                if(i == 0 || j == 0){
                   int sum = 0;
                   if(i != 0) sum += grid[i-1][j];
                   if(j != 0) sum += grid[i][j-1];
                   grid[i][j] = grid[i][j] + sum;
                }else{
                    grid[i][j] = grid[i][j] + Math.min(grid[i][j-1],grid[i-1][j]);
                }
            }
        }
        return grid[len-1][row-1];
    }
}

今天的笔记也就到这里吧,如果稍微帮到你了记得点个喜欢点个关注,也祝大家新年快乐!

上一篇下一篇

猜你喜欢

热点阅读