LeetCode经典算法题:打家劫舍java详解
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1]
输出:4
输入:[2,7,9,3,1]
输出:12
static int maxMoney(int[] nums,int index){
if (nums == null || index < 0) {
return 0;
}
if (index == 0) {
return nums[0];
}
return Math.max(maxMoney(nums,index - 2) + nums[index], maxMoney(nums,index
- 1));
}
static int maxMoney(int[] nums){
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
/*
int[] dp = new int[length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[length - 1];
*/
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
public int rob(int[] nums) {
int length = nums.length;
if (length == 1) {
return nums[0];
} else if (length == 2) {
return Math.max(nums[0], nums[1]);
}
return Math.max(robRange(nums, 0, length - 2), robRange(nums, 1, length -
1));
}
public int robRange(int[] nums, int start, int end) {
int first = nums[start], second = Math.max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
public int rob(TreeNode root) {
int[] rootStatus = dfs(root);
return Math.max(rootStatus[0], rootStatus[1]);
}
public int[] dfs(TreeNode node) {
if (node == null) {
return new int[]{0, 0};
}
int[] l = dfs(node.left);
int[] r = dfs(node.right);
int selected = node.val + l[1] + r[1];
int notSelected = Math.max(l[0], l[1]) + Math.max(r[0], r[1]);
return new int[]{selected, notSelected};
}
给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。
每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。可以假设每个玩家的玩法都会使他的分数最大化。
两个值的时候必然是取较大的,三个值,取一个能使自己分数和最大的,后手必然留较小的给先手,因此先手选一个值加上该较小值最大化
static int maxScore(int[] nums, int l, int r) {
//剩下一个值,只能取该值
if (l == r) {
return nums[l];
}
int selectLeft =0, selectRight=nums.length-1;
//剩下大于两个值,先手选一边(使自己得分最高的一边),后手则选使对手得分最低的一边
if ((r - l) >= 2) {
selectLeft = nums[l] +Math.min(maxScore(nums, l+2, r),maxScore(nums,
l+1, r-1));
selectRight = nums[r] +Math.min(maxScore(nums, l+1, r-1),maxScore(nums,
l, r-2));
}
//剩下两个值,取较大的
if ((r - l) == 1) {
selectLeft = nums[l];
selectRight = nums[r];
}
return Math.max(selectLeft, selectRight);
}
int getScore(int[] nums, int start, int end) {
int selectLeft, selectRight;
int gap = end - start;
if (gap == 0) {
return nums[start];
} else if (gap == 1) { // 此时直接取左右的值就可以
selectLeft = nums[start];
selectRight = nums[end];
} else if (gap >= 2) { // 如果gap大于2,递归计算selectLeft和selectRight
// 计算的过程为什么用min,因为要按照对手也是最聪明的来计算。
int num = getScore(nums, start + 1, end - 1);
selectLeft = nums[start] + min(getScore(nums, start + 2, end), num);
selectRight = nums[end] + min(num, getScore(nums, start, end - 2));
}
return max(selectLeft, selectRight);
}
bool PredictTheWinner(int[] nums) {
int sum = 0;
for (int i : nums) {
sum += i;
int player1 = getScore(nums, 0, nums.size() - 1);
int player2 = sum - player1;
// 如果最终两个玩家的分数相等,那么玩家 1 仍为赢家,所以是大于等于。
return player1 >= player2;
//return getScore(nums, 0, nums.size() - 1) >=0 ;
}
//差值
int getScore(int[] nums, int start, int end) {
if (end == start) {
return nums[start];
}
int selectLeft = nums[start] - getScore(nums, start + 1, end);
int selectRight = nums[end] - getScore(nums, start, end - 1);
return max(selectLeft, selectRight);
}
public boolean PredictTheWinner(int[] nums) {
int length = nums.length;
int[][] dp = new int[length][length];
for (int i = 0; i < length; i++) {
dp[i][i] = nums[i];
}
for (int i = length - 2; i >= 0; i--) {
for (int j = i + 1; j < length; j++) {
//j = i +1 因此可以优化为一维数组,下标位置相同才有值,据此推导其他的值
//Math.max(nums[i] - dp[j][j], nums[j] - dp[j - 1][j - 1]);
dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
}
}
return dp[0][length - 1] >= 0;
}
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
获取一个城市,通过递归找到离该城市最远的城市,标记为已访问,然后逐个向内进行标记
public int findCircleNum(int[][] isConnected) {
int provinces = isConnected.length;
boolean[] visited = new boolean[provinces];
int circles = 0;
for (int i = 0; i < provinces; i++) {
if (!visited[i]) {
dfs(isConnected, visited, provinces, i);
circles++;
}
}
return circles;
}
public void dfs(int[][] isConnected, boolean[] visited, int provinces, int i) {
for (int j = 0; j < provinces; j++) {
if (isConnected[i][j] == 1 && !visited[j]) {
visited[j] = true;
dfs(isConnected, visited, provinces, j);
}
}
}
获取一个城市,先标记与该城市直连的城市(最近的),然后逐步向外扩散寻找
public int bfs(int[][] isConnected) {
int provinces = isConnected.length;
boolean[] visited = new boolean[provinces];
int circles = 0;
Queue<Integer> queue = new LinkedList<Integer>();
for (int i = 0; i < provinces; i++) {
if (!visited[i]) {
queue.offer(i);
while (!queue.isEmpty()) {
int j = queue.poll();
visited[j] = true;
for (int k = 0; k < provinces; k++) {
if (isConnected[j][k] == 1 && !visited[k]) {
queue.offer(k);
}
}
}
circles++;
}
}
return circles;
}
将每个城市看成一个节点,如果两个城市相连,则建立树关系,选出其中一个为head,如果两个树中的节点也相连,则将其中一个head设置为另一个树的head
两个方法 :一个寻找head节点,一个合并树
static int mergeFind(int[][] isConnected){
int provinces = isConnected.length;
int[] head = new int[provinces];
int[] level = new int[provinces];
for (int i = 0; i < provinces; i++) {
head[i] = i;
level[i] = 1;
}
for (int i = 0; i < provinces; i++) {
for (int j = i + 1; j < provinces; j++) {
if (isConnected[i][j] == 1) {
merge(i, j,head,level);
}
}
}
int count = 0;
//找出所有的head
for (int i = 0; i < provinces; i++) {
if (head[i] == i) {
count++;
}
}
return count;
}
//查找head节点
static int find(int x, int[] arr) {
if(arr[x] == x)
return x;
else
arr[x] = find(arr[x],arr);//路径压缩,每一个节点直接能找到head
return arr[x];
}
static void merge(int x, int y,int[] arr,int[] level) {
int i = find(x,arr);
int j = find(y,arr);
//深度比较短的树的head往深度大的树上挂,使合并后的深度尽量小
if(i == j){
return;
}
if(level[i] <= level[j]){
arr[i] = j;
}else{
arr[j] = i;
}
//深度加1
level[j]++;
}
给定由一些正数(代表长度)组成的数组 A ,返回由其中三个长度组成的、面积不为零的三角形的最大周长。
如果不能形成任何面积不为零的三角形,返回 0 。
先小到大排序,假设最长边是最后下标,另外两条边是倒数第二和第三下标,则此时三角形周长最大
n < (n-1) + (n-2),如果不成立,意味着该数组中不可能有另外两个值之和大于n,此时将n左移,重新计算
public int largestPerimeter(int[] A) {
Arrays.sort(A);
for (int i = A.length - 1; i >= 2; --i) {
if (A[i - 2] + A[i - 1] > A[i]) {
return A[i - 2] + A[i - 1] + A[i];
}
}
return 0;
}