Leetcode每日一题 —— 2573. 找出对应 LCP 矩阵的字符串

2026-04-11 15:141阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:
力扣 LeetCode

2573. 找出对应 LCP 矩阵的字符串 - 力扣(LeetCode)

2573. 找出对应 LCP 矩阵的字符串 - 对任一由 n 个小写英文字母组成的字符串 word ,我们可以定义一个 n x n 的矩阵,并满足: * lcp[i][j] 等于子字符串word[i,...,n-1] 和 word[j,...,n-1] 之间的最长公共前缀的长度。 给你一个 n x n 的矩阵 lcp 。返回与 lcp 对应的、按字典序最小的字符串word 。如果不存在这样的字符串,则返回空字符串。 对于长度相同的两个字符串 a 和 b ,如果在 a 和 b...

思路
题目到不是太难

  1. 根据lcp,如果chr[i]=chr[j]那么lcp[i][j]>0,可以确定这个字符的内容。(PS,如果超过26位字符要返回空字符串)
  2. 之前我们通过第一个字符是否相同做了判断,长度是否一致无法确定,所以要进行验证。
  • 对角线肯定是n-1..1
  • 对角线分割的上下三角对称
  • 只验证上三角或者下三角。如果字符不相同,lcp[i][j]=0。否则lcp[i][j]=lcp[i+1][j+1]+1(边界为lcp[x][n-1]=1
    PS 通过递推dp[i][j]时间复杂度也是一样的。

代码

class Solution { public String findTheString(int[][] lcp) { int n = lcp.length; char[] ans = new char[n]; char chr = 'a'; // 获取字符串 for (int i = 0; i < n; i++) { if (ans[i] > 0) { continue; } if (chr > 'z') { return ""; } ans[i] = chr++; for (int j = i + 1; j < n; j++) { if (lcp[i][j] > 0) { ans[j] = ans[i]; } } } // 验证1,对角线 for (int i = 0; i < n; i++) { if (lcp[i][i] != n - i) { return ""; } } // 验证2,上下三角对称 for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (lcp[i][j] != lcp[j][i]) { return ""; } } } // 验证3,上三角数值有效 for (int i = n - 2; i >= 0; i--) { for (int j = n - 1; j > i; j--) { if (ans[i] != ans[j]) { if (lcp[i][j] > 0) { return ""; } } else { // 最右边是1,之后向左上依次+1 if (j == n - 1) { if (lcp[i][j] != 1) { return ""; } } else if (lcp[i][j] != lcp[i + 1][j + 1] + 1) { return ""; } } } } return new String(ans); } } 网友解答:


--【壹】--:

怎么做到这么短的,看完你的,我觉得我写的简直就是一坨

class UnionFind: def __init__(self, size): self.parent = list(range(size)) self.rank = [0] * size def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, a, b): rootA = self.find(a) rootB = self.find(b) if rootA == rootB: return if self.rank[rootA] < self.rank[rootB]: self.parent[rootA] = rootB elif self.rank[rootA] > self.rank[rootB]: self.parent[rootB] = rootA else: self.parent[rootB] = rootA self.rank[rootA] += 1 def connected(self, a, b): return self.find(a) == self.find(b) class Solution: def validate(self, lcp: List[List[int]]) -> bool: n = len(lcp) for i in range(n): if lcp[i][i] != n - i: return False for j in range(i + 1, n): if lcp[i][j] > n - i: return False return all(lcp[i][j] == lcp[j][i] for i in range(n) for j in range(i+1, n)) def check_lcp(self, word: List[str], lcp: List[List[int]]) -> bool: n = len(word) for i in range(n): for j in range(i + 1, n): expected = 0 if word[i] == word[j]: if i + 1 < n and j + 1 < n: expected = lcp[i + 1][j + 1] + 1 else: expected = 1 if lcp[i][j] != expected: return False return True def findTheString(self, lcp: List[List[int]]) -> str: """ 1. LCP矩阵对称 2. LCP矩阵对角线从n递减至1 3. 每行元素必定小于等于 (n - i) 4. LCP[i][j] > 0,那么字符串位置[i]和[j]至少是相同的,否则没有common prefix 5. 构造的字符串,必须是[a-z]+ """ n = len(lcp) ans = [] # 特判 if not self.validate(lcp): return "" # 构造Union Find uf = UnionFind(n) for i in range(n): for j in range(i + 1, n): if lcp[i][j] > 0: uf.union(i, j) # 从最小索引寻找同一组连通分量 # mp: root -> char mp = {} ch = ord('a') for i in range(n): root = uf.find(i) if root not in mp: mp[root] = chr(ch) ch += 1 if ch > ord('a') + 26: return "" ans.append(mp[root]) # 检查ans以及ans构造的LCP if not self.check_lcp(ans, lcp): return "" return ''.join(ans)


--【贰】--:

今天做了三道 hot 100 的经典题。


53. 最大子数组和 - 力扣(LeetCode)

动态规划写法:

class Solution { public: int maxSubArray(vector<int>& nums) { // 这题似乎是基础动态规划题 // 当当前和为负数时就从下一个位置重新开始,期间不断更新结果 int res=nums[0]; int currSum=0; for(int i=0;i<nums.size();i++){ if(currSum<0){ currSum=nums[i]; }else{ currSum+=nums[i]; } res=max(res,currSum); } return res; } };

分治写法,稍微慢一些,不过这个分治处理的思想值得回顾:

class Solution { public: int maxSubArray(vector<int>& nums) { // 这题还可以用分治法做 // 分治法要考虑中间横跨的部分 auto div = [&](auto&& self, int left, int right){ if(left==right){ return nums[left]; } int mid=(left+right)>>1; // 拿到左右各自的最大和 int leftMaxSum=self(self,left,mid); int rightMaxSum=self(self,mid+1,right); // 除此之外还要算一下横跨中间部分的最大和 // 以 mid 为中线向两边延展计算后缀和前缀最大和 // 之所以是后缀和前缀,是因为要保证是连续子数组 int postSum=-1e9; int currSum=0; for(int i=mid;i>=left;i--){ currSum+=nums[i]; postSum=max(postSum,currSum); } int preSum=-1e9; currSum=0; for(int i=mid+1;i<=right;i++){ currSum+=nums[i]; preSum=max(preSum,currSum); } // 要不取左边的、要不取右边的,要不取横跨中间的 return max(leftMaxSum,max(rightMaxSum,postSum+preSum)); }; return div(div,0,nums.size()-1); } };


42. 接雨水 - 力扣(LeetCode)

极其经典的接雨水,短木板原理。

class Solution { public: int trap(vector<int>& height) { // 依旧短木板原理 // 从左至右、从右至左扫描生成前后缀最大值 int n=height.size(); vector<int> prefix(n,0),postfix(n,0); prefix[0]=height[0]; postfix[n-1]=height[n-1]; for(int i=1;i<n;i++){ prefix[i]=max(prefix[i-1],height[i]); postfix[n-1-i]=max(postfix[n-i],height[n-1-i]); } // 然后再扫描一遍,每个位置前缀和后缀最低高度决定了储水量 int res=0; for(int i=1;i<n-1;i++){ // 别忘了每个位置自己还有个高度要减去 res+=max(0,min(prefix[i-1],postfix[i+1])-height[i]); } return res; } };


300. 最长递增子序列 - 力扣(LeetCode)

经典动态规划题,注意最长递增子序列不一定是以 n-1 结尾,因此在递推过程中就要不断更新结果。

O(n^2) 解法:

class Solution { public: int lengthOfLIS(vector<int>& nums) { // 最长递增子序列,动规题 // dp[i] 代表 0...i 的最长严格递增子序列长度 int n=nums.size(); vector<int> dp(n,0); // 显然 dp[0] 为 1 dp[0]=1; int res=1; // 至少是 1 // 发现输入规模不大,这题可以 O(n^2) 解决 for(int i=1;i<n;i++){ dp[i]=1; // 默认情况下为 1 for(int j=0;j<i;j++){ if(nums[j]<nums[i]){ // 出现了严格递增,在前面的状态上 +1 取最大值 dp[i]=max(dp[i],dp[j]+1); } } // 注意!递增子序列不一定是以 n-1 结尾,中途可能有最长递增子序列! // 因此在推演过程中就要根据 dp[i] 更新结果 res=max(res,dp[i]); } return res; } };


--【叁】--:

每日一题


--【肆】--:

并查集本身就必须得有一大段,这没办法,都是正常情况


--【伍】--:

做晕了TAT


--【陆】--:
力扣 LeetCode

994. 腐烂的橘子 - 力扣(LeetCode)

994. 腐烂的橘子 - 在给定的m x n网格grid中,每个单元格可以有以下三个值之一: * 值0代表空单元格; * 值1代表新鲜橘子; * 值2代表腐烂的橘子。 每分钟,腐烂的橘子周围4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回-1。 示例...

class Solution: def orangesRotting(self, grid: List[List[int]]) -> int: ans = 0 m, n = len(grid), len(grid[0]) dq = deque() cnt = 0 for i in range(m): for j in range(n): if grid[i][j] == 1: cnt += 1 elif grid[i][j] == 2: dq.append((i, j)) while cnt and len(dq) > 0: ans += 1 l = len(dq) for _ in range(l): i, j = dq.popleft() if i - 1 >= 0 and grid[i - 1][j] == 1: grid[i - 1][j] = 2 cnt -= 1 dq.append((i - 1, j)) if i + 1 < m and grid[i + 1][j] == 1: grid[i + 1][j] = 2 cnt -= 1 dq.append((i + 1, j)) if j - 1 >= 0 and grid[i][j - 1] == 1: grid[i][j - 1] = 2 cnt -= 1 dq.append((i, j - 1)) if j + 1 < n and grid[i][j + 1] == 1: grid[i][j + 1] = 2 cnt -= 1 dq.append((i, j + 1)) return -1 if cnt else ans