列表

详情


LCP 57. 打地鼠

欢迎各位勇者来到力扣城,本次试炼主题为「打地鼠」。 middle_img_v2_d5d09656-0616-4a80-845e-ece461c5ba9g.png{:height="200px"} 勇者面前有一个大小为 3*3 的打地鼠游戏机,地鼠将随机出现在各个位置,moles[i] = [t,x,y] 表示在第 t 秒会有地鼠出现在 (x,y) 位置上,并于第 t+1 秒该地鼠消失。

勇者有一把可敲打地鼠的锤子,初始时刻(即第 0 秒)锤子位于正中间的格子 (1,1),锤子的使用规则如下:

请返回勇者最多能够敲击多少只地鼠。

注意:

示例 1:

输入: moles = [[1,1,0],[2,0,1],[4,2,2]]

输出: 2

解释: 第 0 秒,锤子位于 (1,1) 第 1 秒,锤子移动至 (1,0) 并敲击地鼠 第 2 秒,锤子移动至 (2,0) 第 3 秒,锤子移动至 (2,1) 第 4 秒,锤子移动至 (2,2) 并敲击地鼠 因此勇者最多可敲击 2 只地鼠

示例 2:

输入:moles = [[2,0,2],[5,2,0],[4,1,0],[1,2,1],[3,0,2]]

输出:3

解释: 第 0 秒,锤子位于 (1,1) 第 1 秒,锤子移动至 (2,1) 并敲击地鼠 第 2 秒,锤子移动至 (1,1) 第 3 秒,锤子移动至 (1,0) 第 4 秒,锤子在 (1,0) 不移动并敲击地鼠 第 5 秒,锤子移动至 (2,0) 并敲击地鼠 因此勇者最多可敲击 3 只地鼠

示例 3:

输入:moles = [[0,1,0],[0,0,1]]

输出:0

解释: 第 0 秒,锤子初始位于 (1,1),此时并不能敲击 (1,0)、(0,1) 位置处的地鼠

提示:

原站题解

去查看

上次编辑到这里,代码来自缓存 点击恢复默认模板
class Solution { public: int getMaximumNumber(vector<vector<int>>& moles) { } };

golang 解法, 执行用时: 440 ms, 内存消耗: 14.1 MB, 提交时间: 2023-10-26 23:47:11

// 动态规划 f(i)表示只考虑前i个地鼠,打中了第i个地鼠的情形能够打到的最大地鼠数量,f(i)=max(f(j)+1),
//其中j<i,且i与j之间的时间间隔t大于两者距离d
// 这里还有一个重要的优化,因为题目说明了是3X3的矩阵,
//两个位置的最大距离是4,那么当t>=4时,从前面任意一个位置都有足够的时间过来,
//所以可以直接取前缀最大值。没有这一步就会超时。
// 参考了优秀题解https://leetcode.cn/problems/ZbAuEH/solution/by-tsreaper-ey5v/ 对于时间为0的地鼠处理有所不同
func getMaximumNumber(moles [][]int) int {
	// 必须按照时间排序,否则在考虑一个地鼠是否可达的时候会出问题。比如说(0,2,0)显然是不可达的,因为初始位置在(1,1)。考虑一种情形,(1,1,0)是可达的,
	//如果(0,2,0)紧跟其后,就会误判(0,2,0)可达。我把这个叫做时间倒流陷阱。
	sort.Slice(moles, func(i, j int) bool {
		return moles[i][0] < moles[j][0]
	})
	n := len(moles)
	dp := make([]int, n)
	preMax := make([]int, n)
	ans := 0
	for i := 0; i < n; i++ {
		for j := i - 1; j >= 0; j-- {
			t := moles[i][0] - moles[j][0]
			// 可以从前面任意一个位置过来,所以直接取前缀最大值
			if t >= 4 {
				dp[i] = max(dp[i], preMax[j]+1)
				break
			}
			// dp[j]==0表示这个地鼠是不可能达到的
			if dp[j] > 0 && t >= absDiff(moles[i][1], moles[j][1])+absDiff(moles[i][2], moles[j][2]) {
				dp[i] = max(dp[i], dp[j]+1)
			}
		}
		// 考虑从初始位置是否可达
		if dp[i] == 0 && absDiff(moles[i][0], 0) >= absDiff(moles[i][1], 1)+absDiff(moles[i][2], 1) {
			dp[i] = 1
		}
		ans = max(ans, dp[i])
		if i == 0 {
			preMax[0] = dp[0]
		} else {
			preMax[i] = max(preMax[i-1], dp[i])
		}
	}
	return ans
}

func absDiff(i, j int) int {
	if i > j {
		return i - j
	}
	return j - i
}

golang 解法, 执行用时: 508 ms, 内存消耗: 22.2 MB, 提交时间: 2023-10-26 23:46:40

func getMaximumNumber(moles [][]int) int {
	sort.Slice(moles, func(i, j int) bool {
		return moles[i][0] < moles[j][0]
	})
	type point struct{ x, y int }
	var molesByTime [][]*point
	var timeSort []int
	for _, mole := range moles {
		if len(timeSort) == 0 || mole[0] > timeSort[len(timeSort)-1] {
			timeSort = append(timeSort, mole[0])
			molesByTime = append(molesByTime, []*point{})
		}
		molesByTime[len(molesByTime)-1] = append(molesByTime[len(molesByTime)-1], &point{mole[1], mole[2]})
	}
	var preTime int
	//0时刻,只有1,1有锤子,所以要保证其他8个位置,不会因为有地鼠而得到正数
	dp := [3][3]int{{-3, -3, -3}, {-3, 0, -3}, {-3, -3, -3}}
	for i, points := range molesByTime {
		for j := 0; j < 4 && preTime+1+j <= timeSort[i]; j++ {
			var dpNew [3][3]int
			//硬编码了9个点分别依赖哪几个点
			dpNew[0][0] = max(dp[0][0], max(dp[0][1], dp[1][0]))
			dpNew[0][1] = max(dp[0][1], max(dp[0][0], max(dp[0][2], dp[1][1])))
			dpNew[0][2] = max(dp[0][2], max(dp[0][1], dp[1][2]))
			dpNew[1][0] = max(dp[1][0], max(dp[0][0], max(dp[2][0], dp[1][1])))
			dpNew[1][1] = max(dp[1][1], max(dp[0][1], max(dp[1][0], max(dp[1][2], dp[2][1]))))
			dpNew[1][2] = max(dp[1][2], max(dp[0][2], max(dp[1][1], dp[2][2])))
			dpNew[2][0] = max(dp[2][0], max(dp[1][0], dp[2][1]))
			dpNew[2][1] = max(dp[2][1], max(dp[2][0], max(dp[2][2], dp[1][1])))
			dpNew[2][2] = max(dp[2][2], max(dp[1][2], dp[2][1]))
			dp = dpNew
		}
		for _, p := range points {
			dp[p.x][p.y]++
		}
		preTime = timeSort[i]
	}
	var res int
	for r := 0; r < 3; r++ {
		for c := 0; c < 3; c++ {
			res = max(res, dp[r][c])
		}
	}
	return res
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

python3 解法, 执行用时: 9484 ms, 内存消耗: 68.6 MB, 提交时间: 2023-10-26 23:46:04

class Solution:
    def getMaximumNumber(self, moles: List[List[int]]) -> int:
        moles.sort(key = lambda x:x[0])
        n = len(moles)
        # dp[i][j]表示当我们遇见第i个地鼠时,且锤子位置在j时能够打到的最大地鼠个数,
        dp = [[-float('inf') for _ in range(9)] for _ in range(n + 1)]
       
        dp[0][4] = 0
        pre = 0
        d = [[0 for _ in range(9)] for _ in range(9)]
        for i in range(9):
            for j in range(9):
                d[i][j] = abs(i // 3 - j // 3) + abs(i % 3 - j % 3)

        for i in range(1,n + 1):
            t,x,y = moles[i - 1]
            diff = t - pre
            pre = t     
            for last in range(9):
                for next_ in range(9):
                    if d[last][next_] <= diff:
                        dp[i][next_] = max(dp[i][next_],dp[i - 1][last])
            dp[i][x * 3 + y] += 1
                        
            
        return max(dp[-1])

java 解法, 执行用时: 106 ms, 内存消耗: 69.7 MB, 提交时间: 2023-10-26 23:45:26

class Solution {
    public int getMaximumNumber(int[][] m) {
        
        //按时间排序
        Arrays.sort(m, Comparator.comparingInt((a)->(a[0])));
        //拷贝一份数组m,把起始位置(0,1,1)考虑进去。arraycopy还挺慢的,伙计们可以换别的实现
        int[][] q = new int [m.length + 1][3];
        System.arraycopy(m, 0, q, 1, m.length);
        q[0] = new int[] {0, 1, 1};

        // dp[i][0] 表示前 i 只地鼠中,第 i 只地鼠不打的情况下,获得的最大分数。
        // dp[i][1]表示前 i 只地鼠中,第 i 只地鼠打的情况下,获得的最大分数。
        int[][] dp = new int[q.length][2];
        // i 可以从1开始遍历,因为在dp[0][0]和dp[0][1]的值为0;
        for(int i = 1; i < q.length; i++){
            
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
            
            //假如无法从0状态(初始状态)到达i状态(当前状态),则dp[i][1]=0;
            if(!time(q, i, 0))    continue;
            
            for(int j = i - 1; j >= 0; j--){
                //time函数用来判断j能否到达i;
                if(time(q, i, j)){
                    dp[i][1]=Math.max(dp[j][1] + 1, dp[i][1]);
                }
                //相差4打破循环。
                if(q[i][0] - q[j][0] >= 4){
                    dp[i][1]=Math.max(dp[j][0] + 1, dp[i][1]);
                    break;
                }
            }
        }
        return (int)Math.max(dp[m.length][0], dp[m.length][1]);
    }
    boolean time(int[][] m, int i, int j){
        int t = m[i][0] - m[j][0];
        int need = Math.abs(m[i][1] - m[j][1]) + Math.abs(m[i][2] - m[j][2]);
        return need <= t;
    }
}

cpp 解法, 执行用时: 1324 ms, 内存消耗: 242.9 MB, 提交时间: 2023-10-26 23:44:30

class Solution {

public:
    int getMaximumNumber(vector<vector<int>>& moles) {
        // 把所有时间 0 出现的地鼠排除
        vector<vector<int>> A;
        bool flag = false;
        for (auto &mole : moles) {
            if (mole[0] == 0) {
                // 看一下有没有时间 0 位于 (1, 1) 的地鼠,一开始就能打
                if (mole[1] == 1 && mole[2] == 1) flag = true;
            } else {
                A.push_back(mole);
            }
        }
        // 初始位置位于 (1, 1)
        A.push_back(vector<int>{0, 1, 1});

        int n = A.size();
        sort(A.begin(), A.end());
        vector<int> f(n), g(n);
        int ans = 0;
        for (int i = 1; i < n; i++) {
            f[i] = -1e8;  // 打了前i只地鼠,最多能打几只
            for (int j = i - 1; j >= 0; j--) {
                int t = A[i][0] - A[j][0], d = abs(A[i][1] - A[j][1]) + abs(A[i][2] - A[j][2]);
                // 能从任何位置移过来,用前缀 max 更新答案
                if (t > 4) { f[i] = max(f[i], g[j] + 1); break; }
                // 虽然有时间限制,但移过来能来得及,更新答案
                else if (d <= t) f[i] = max(f[i], f[j] + 1);
            }
            ans = max(ans, f[i]);
            g[i] = max(g[i - 1], f[i]);
        }
        return ans + (flag ? 1 : 0);
    }
};

上一题