【LeetCode】Sama的个人记录_25

 

 

【Q108】(md) 有序矩阵中第k小的元素
 
给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
 
请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。
 
示例
matrix = [ [ 1, 5, 9 ]
    , [10, 11, 13]
    , [12, 13, 15] ],
k = 8,
返回 13。

class Solution {
	/*
	 * 基本思路:将二维数组转化为一维数组,再排序
	 */
    public int kthSmallest(int[][] matrix, int k) {
    	int len = matrix.length;
    	int[] newMatrix = new int[len * len];
    	
    	int index = 0;
    	for(int[] row : matrix) {
    		for(int n : row) {
    			newMatrix[index++] = n; 
    		}
    	}
    	Arrays.sort(newMatrix);
    	return newMatrix[k - 1];
    }
    // 这肯定是不行的———任意一个无序数组都可以使用该方法,并没有用到该题在二维上都具有的递增性
    // 显然存在更优解
}
class Solution {
	/*
	 * (★☆★)
	 * 【多路归并】————【维护一个最小堆】
	 * 
	 * 思路:维护一个堆(每次poll出的为最小值),可以看做将matrix数组挂在这个堆上(每次poll出一个int[]元素,就用该行的下一个offer上)
	 */
    public int kthSmallest(int[][] matrix, int k) {
    	PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return a[0] - b[0];
            }
        });
    	int len = matrix.length;
    	for(int i = 0; i < len; i++) {
    		pq.offer(new int[]{matrix[i][0], i, 0});	// 这个int[],下标0是值,下标1是第几行,下标2是第几列
    	}
    	for(int i = 0; i < k - 1; i++) {
    		int[] now = pq.poll();
    		if(now[2] < len - 1) {
    			pq.offer(new int[]{matrix[ now[1] ][ now[2] + 1 ], now[1], now[2] + 1});
    		}
    	}
    	return pq.poll()[0];
    }
}
class Solution {
	/*
	 * 【二分法】
	 * 
	 * 这类用【值】而不是【下标】进行二分的题目,都可以分成两部分求解:
	 * 		1.值二分  
	 * 		2.如何根据二分获得的值得到某个结果,再用于二分的条件判断
	 * 
	 * 先来解决第二个问题:本题通过从数组左下角走出的一条路径,将数组分割为两部分,并计算出【小于等于mid的值的个数】
	 * 
	 * 在【个人记录_20】的【lc_1300 转变数组后最接近目标值的数组和】和【lc_5438 制作m束花需要的最小天数】中最早接触这种【值二分】
	 * 当时的难点在于【二分的边界问题】,因而总结出那种【试探型值二分】的边界问题的套路化写法
	 * 
	 * 然而在本题中,难点依旧是二分的边界问题,却不再是【试探型值二分】,而是像普通二分那样的【缩小型二分(减治思想)】
	 * 
	 * 根据计算出的【小于等于mid的值的个数】,尝试去理解下面这段话:
	 * 		1. 当这个count小于k时,最终结果比不可能是mid			[1, 5, 9, 9, 9] 找第3小的数,mid为5时count为2,最终结果必不是5         (>5)
	 * 		2. 当这个count大于等于k时,最终结果有可能就是这个mid		[1, 5, 9, 9, 9] 找第3小的数,mid为9时count为5,最终结果不排除是9的可能    (<=9)
	 * 
	 * 所以在二分时,这两种情况分别对应: left = mid + 1  和 right = mid 
	 * 
	 * 
	 * 好了,二分的边界代码我们已经得到了,却遇到了另一个难题,这是【值二分】且【减治二分】同时使用时必然遇到的问题:
	 * 		【如何保证返回的left一定存在于数组之中?】
	 * 换句话说
	 * 		【值二分通常是在[试探]哪个数满足条件,这个数是任意的值————我们的套路化边界写法,也是建立在[值随意取,只要试探出来了就行]的思路之上的;可本题不是】
	 * 
	 * 安全性在于:
	 * 		while循环的条件时是left < right,终止循环必然是因为left == right
	 * 		最后的角逐,无论是13,15还是14,15(事实上只有这两种情况),最终结束的原因都是 left = mid + 1 之后,left == right
	 * 		为什么会left = mid + 1 ?	因为count突变。
	 * 		count突变说明触碰到了某个数组中的值,且此时范围缩小到left == right,此时返回left还是right都是可以的。
	 * 		
	 * 			
	 */
    public int kthSmallest(int[][] matrix, int k) {
    	int len = matrix.length;
    	int left = matrix[0][0];
    	int right = matrix[len - 1][len - 1];
    	while(left < right) {
    		int mid = (left + right) / 2;
    		if(count(matrix, len, mid) >= k) {
    			right = mid;
    		}else {
    			left = mid + 1;
    		}
    	}
    	return left;
    }
    
    private int count(int[][] matrix, int len, int mid) {
    	int i = len - 1;
    	int j = 0;
    	int count = 0;
    	while(i >= 0 && j < len) {
    		if(matrix[i][j] <= mid) {
    			count += (i + 1);
    			j++;
    		}else {
    			i--;
    		}
    	}
    	return count;
    }
    // 通过此题,对【二分】二字有更深刻的认知
}
>>> 总结一下这三个算法:
>>> 第一种(二维转一维)		没有用到该矩阵的任何性质
>>> 第二种(多路归并)		用到了该矩阵在水平方向的递增特性
>>> 第三种(二分)			用到了该矩阵在水平和垂直两个方向的递增特性

 

 

【Q174】(hd) 地下城游戏
 
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
 
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
 
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
 
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
 
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
 
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
 
-2(K)  -3     3
-5    -10   1
10    30    -5 (P )
 
说明:

  1. 骑士的健康点数没有上限。
  2. 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
class Solution {
    /*
    【反向DP】————不是骑士救公主,而是公主去寻找骑士

    dp值的含义是:勇士从此处出发到达公主房间,所需要的最小血量
    细节:如果值为0或负数,说明骑士血量为多少都可以,因此取此dp值为 1

    可是,为什么要反向dp呢?
    
    正向dp思路:正向dp一次求出骑士会受到了最大伤害(这是可以求的),作为二分上限。1作为二分下限。
               再写一个方法(参数为HP,动态规划),用来判断某个起始血量下骑士能否走到公主的面前。然后二分就可以。

    问题在于,那个“用来判断某个起始血量下骑士能否走到公主的面前“的方法是个正向dp,写一写才发现,因为"血包"的存在,更倾向于回溯。
             二分的复杂度是可控的,而递归就不可控了。

    一个序列中,如果包含正数和负数,就要尤为小心了——符号的改变有可能推翻你之前建立的路径,会让dp的根基(上一个状态)毫无意义。这道题又一次警示了我们。
     */
    public int calculateMinimumHP(int[][] dungeon) {
        int M = dungeon.length;
        int N = dungeon[0].length;
        int dp[][] = new int[M][N];

        for (int i = M - 1; i >= 0; i--) {
            for (int j = N - 1; j >= 0; j--) {
                if (i == M - 1 && j == N - 1) {
                    dp[i][j] = (1 - dungeon[i][j]) >= 1 ? 1 - dungeon[i][j] : 1;
                    continue;
                }
                int min = Math.min((i + 1 <= M - 1 ? dp[i + 1][j] : Integer.MAX_VALUE), (j + 1 <= N - 1 ? dp[i][j + 1] : Integer.MAX_VALUE));
                dp[i][j] = (min - dungeon[i][j]) >= 1 ? min - dungeon[i][j] : 1;
            }
        }
        return dp[0][0];
    }
}

 
 

 

 

 

 

 

 

 

 

 
 

 

 

Qs from https://leetcode-cn.com
♥ loli suki
♣ end

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值