【LeetCode】Sama的个人记录_21

 

 

【Q1014】(md) 最佳观光组合
 
给定正整数数组 A,A[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的距离为 j - i。
 
一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。
 
返回一对观光景点能取得的最高分。
 
示例:
输入:[8,1,5,2,6]
输出:11
解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11

class Solution {
     
	/*
	 * 关键:将题设中的 A[i]+  A[j] - (j - i) 即 A[i] + A[j] + i - j 分解为 A[i]+i 和 A[j]-j
	 * 
	 * 在一次遍历中:
	 * 		每次尝试更新res也就是 A[j] - j + maxi 的值
	 * 		其中maxi的含义是:A[i]+i 的最大值,因为虽然它们被分开为独立的两部分,i仍需满足i
    public int maxScoreSightseeingPair(int[] A) {
     
    	int res = 0;
    	int maxi = A[0] + 0;
    	for(int j = 1; j < A.length; j++) {
     
    		res = Math.max(res, A[j] - j + maxi);		// 维护(尝试更新)最终结果的最大值
    		maxi = Math.max(maxi, A[j] + j);			// 遍历到j的某处时,不仅维护最大值,同时维护在i<=j位置maxi的最大值
    	}
    	return res;
    }
}

 

 

【Q45】(hd) 跳跃游戏Ⅱ
 
给定一个非负整数数组,你最初位于数组的第一个位置。
 
数组中的每个元素代表你在该位置可以跳跃的最大长度。
 
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
 
示例:
   输入: [2,3,1,1,4]
   输出: 2
 
说明: 假设你总是可以到达数组的最后一个位置。

class Solution {
     
	/*
	 *【贪心+dp】
	 * 
	 * 核心思路是:下标对应的值为跳到该处的最小步数;遍历到该处时(其实值应该已经填完了),去填它之后nums[i]个值
	 * 也就是说,这个dp表,遍历到某处,填的是它之后的表值;当已经被填过时(!=0),就跳过去不填
	 * 
	 * 状态转移方程:在之后的nums[i]个位置填上dp[i] + 1
	 * 
	 * 用一个例子很容易明白:
	 * nums = [1, 1, 3, 5, 1, 1, 1, 1, 1, 1, 1]
	 * dp   = [0, 1, 2, 3, 3, 3, 4, 4, 4, 5, 6]
	 * 最少6步
	 */
    public int jump(int[] nums) {
     
    	int len = nums.length;
    	int[] dp = new int[len];
    	for(int i = 0; i < len; i++) {
     
    		for(int j = 1; j <= nums[i]; j++) {
     
    			if((i + j) < len && dp[i + j] == 0) {
     	// 确保未越界,且该处未被填过时
    				dp[i + j] = dp[i] + 1;				// 状态转移方程		
    			}
    		}
    	}
    	return dp[len - 1];
    }
}

// 毕竟是两层for循环,我们需要优化一下
// 我们用一个head用来记录此时未被填值的下标最大位置,并维护它;第二个for直接尝试从head开始填值就可以
class Solution_2 {
     
    public int jump(int[] nums) {
     
    	int len = nums.length;
    	int[] dp = new int[len];
    	int head = 1;
    	for(int i = 0; i < len; i++) {
     
    		for(int j = head; j <= i + nums[i]; j++) {
     
    			if(j >= len)	break;
    			dp[j] = dp[i] + 1;
    		}
    		head = Math.max(i + nums[i] + 1, head);
    	}
    	return dp[len - 1];
    }
}
/*
 * 这是官解给出的标准【贪心】写法:
 * 不需要dp表,直接用steps记录步数,并维护两个值:end和maxPosition
 * 注意:
 * 		1.maxPosition的含义是下一步可以到达的最远下标
 * 		2.当i位于end处时,steps就+1,并把end更新为maxPosition
 * 
 * 这样写法的贪心和上面的dp是本质一致的(steps的值完全对应dp表值),都是获取到达此处的最优解,没有考虑全局最优解
 * 这样使用多个记录值,的确没有dp直接都给填上好理解,但时间上相差无几,空间节省了不少
 */
class Solution_3 {
     
    public int jump(int[] nums) {
     
        int len = nums.length;
        int end = 0;
        int maxPosition = 0; 
        int steps = 0;
        for (int i = 0; i < len - 1; i++) {
     
            maxPosition = Math.max(maxPosition, i + nums[i]); 
            if (i == end) {
     
                end = maxPosition;
                steps++;
            }
        }
        return steps;
    }
}

 

 

【Q47】(md) 全排列Ⅱ
给定一个可包含重复数字的序列,返回所有不重复的全排列。
 
示例:

输入: [1,1,2]
输出: [ [1,1,2], [1,2,1], [2,1,1] ]
 
注:【Q46 全排列】的进阶

class Solution
 {
     
	/*
	 * 【回溯】
	 * 
	 * 这道题目在回溯时,会遇到两个问题:
	 * 
	 * 一是保证每个元素都被遍历一次。比如[1, 2, 3],防止出现1, 1, 1:
	 * 			在traceback的参数中,不仅需要一个暂存串buffer,还需要一个路径暂存串———记录的是下标,防止重复
	 * 			这个路径(下标)记录串,同样需要利用remove手动回溯
	 * 			(这个路径暂存串用ArrayList就行,这里我使用了HashSet没有必要)
	 * 二是防止结果集的重复。比如[1, 1, 2]的到6个结果,但显然存在重复
	 * 			解决方法是,在回溯得到res之后,多res再进行一次修正得到finalRes
	 */
    public List<List<Integer>> permuteUnique(int[] nums) {
     
    	ArrayList<Integer> buffer = new ArrayList<>();
    	HashSet<Integer> set = new HashSet<>();
    	traceback(nums, buffer, set);
    	List<List<Integer>> finalRes = new ArrayList<>();
    	for(List<Integer> each : res) {
     
    		if(!finalRes.contains(each)) {
     
    			finalRes.add(each);
    		}
    	}
    	return finalRes;
    }
    
    private List<List<Integer>> res = new ArrayList<>();
    
    private void traceback(int[] nums, ArrayList<Integer> buffer, HashSet<Integer> set) {
     
    	if(buffer.size() == nums.length) {
     
    		res.add(new ArrayList<Integer>(buffer));
    		return;
    	}
    	for(int i = 0; i < nums.length; i++) {
     
    		if(!set.contains(i)) {
     
    			buffer.add(nums[i]);
    			set.add(i);
    			traceback(nums, buffer, set);
    			set.remove(i);
    			buffer.remove(buffer.size() - 1);
    		}
    	}
    }
    /*
     * 总结:
     * 1.路径暂存串(记录下标)的思路比较巧妙与经典,而且很容易理解
     * 2.我们在回溯时,完全没有使用【减枝】,而是在回溯后修正res去重为finalRes,这显然效率不高
     * 	  那么该如何通过剪枝来优化算法呢?
     * 		每次出现重复时,都有一个特点:此时的即将加入buffer的数字,是刚刚撤销的那个数字!
     * 		比如[1, 1, 2],有三个大分支 1, 1, 2,在到第二个1时,刚刚撤销的数字就是第一个1 !
     * 		用一个used数组记录【刚刚撤销的数字】,就能实现剪枝
     */
}

 
 

 

 

 

 

 

 

 

 

 
 

 

 

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

你可能感兴趣的:(Leetcode,算法,动态规划,leetcode)