目录
第一题
题目来源
题目内容
解决方法
方法一:滑动窗口
方法二:哈希表和双指针
方法三:动态规划
第二题
题目来源
题目内容
解决方法
方法一:深度优先搜索(DFS)
方法二:树结构
第三题
题目来源
题目内容
解决方法
方法一:遍历交换
方法二:递归
方法三:字典序法
30. 串联所有单词的子串 - 力扣(LeetCode)
import java.util.*;
class Solution {
public List findSubstring(String s, String[] words) {
List result = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) {
return result;
}
int wordLength = words[0].length();
int totalLength = words.length * wordLength;
Map wordCount = new HashMap<>();
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
for (int i = 0; i <= s.length() - totalLength; i++) {
Map tempCount = new HashMap<>(wordCount);
int count = words.length;
for (int j = 0; j < totalLength; j += wordLength) {
String word = s.substring(i + j, i + j + wordLength);
if (tempCount.containsKey(word)) {
int freq = tempCount.get(word);
if (freq == 1) {
tempCount.remove(word);
} else {
tempCount.put(word, freq - 1);
}
count--;
} else {
break;
}
}
if (count == 0) {
result.add(i);
}
}
return result;
}
}
复杂度分析:
时间复杂度:
空间复杂度:
LeetCode运行结果:
除了使用滑动窗口的方法外,还可以使用基于哈希表和双指针的解决方案。
这个解决方案使用了双指针的方法。
import java.util.*;
class Solution {
public List findSubstring(String s, String[] words) {
List result = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) {
return result;
}
int wordLength = words[0].length();
int totalLength = words.length * wordLength;
Map wordCount = new HashMap<>();
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
for (int i = 0; i <= s.length() - totalLength; i++) {
Map tempCount = new HashMap<>(wordCount);
int count = words.length;
int left = i;
while (count > 0) {
String word = s.substring(left, left + wordLength);
if (tempCount.containsKey(word) && tempCount.get(word) > 0) {
tempCount.put(word, tempCount.get(word) - 1);
count--;
left += wordLength;
} else {
break;
}
}
if (count == 0) {
result.add(i);
}
}
return result;
}
}
复杂度分析:
时间复杂度:
空间复杂度:
综上所述,该基于哈希表和双指针的解决方案的时间复杂度为O(N * L * M),空间复杂度为O(M)。与滑动窗口的方法相比,其时间复杂度稍高,但实际运行效果可能会因具体情况而有所不同。
LeetCode运行结果:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
public List findSubstring(String s, String[] words) {
List result = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) {
return result;
}
int wordLen = words[0].length();
int wordCount = words.length;
int windowLen = wordLen * wordCount;
Map wordCountMap = new HashMap<>();
for (String word : words) {
wordCountMap.put(word, wordCountMap.getOrDefault(word, 0) + 1);
}
for (int i = 0; i < s.length() - windowLen + 1; i++) {
Map tempCountMap = new HashMap<>(wordCountMap);
for (int j = 0; j < wordCount; j++) {
String word = s.substring(i + j * wordLen, i + (j + 1) * wordLen);
if (!tempCountMap.containsKey(word)) {
break;
}
tempCountMap.put(word, tempCountMap.get(word) - 1);
if (tempCountMap.get(word) == 0) {
tempCountMap.remove(word);
}
if (tempCountMap.isEmpty()) {
result.add(i);
break;
}
}
}
return result;
}
}
这段代码使用了一个wordCountMap哈希表来记录words数组中每个单词出现的次数。然后,通过两个嵌套的循环遍历字符串s,在每个位置检查以该位置为起点的子串是否包含words数组中的所有单词。
其中,外层循环遍历了所有可能的起始位置,内层循环遍历了窗口内的所有单词。在内层循环中,使用tempCountMap记录当前窗口内各个单词的剩余次数,同时进行相应的更新和判断操作。当tempCountMap为空时,表示窗口内包含了words数组中的所有单词,将该起始位置加入结果列表。
最后,返回结果列表即可。
复杂度分析:
总结起来,该动态规划解决方案的时间复杂度为O(n * k * m),空间复杂度为O(k)。其中,n为字符串s的长度,k为words数组的长度,m为单词的平均长度。
LeetCode运行结果:
1993. 树上的操作 - 力扣(LeetCode)
使用深度优先搜索(DFS)来实现 LockingTree 类。可以通过递归地遍历树的节点来判断锁的状态,以及执行相应的锁定、解锁和升级操作。
具体思路如下:
import java.util.*;
class LockingTree {
private int[] parent;
private Map locks;
private Map> children;
public LockingTree(int[] parent) {
this.parent = parent;
this.locks = new HashMap<>();
this.children = new HashMap<>();
for (int i = 0; i < parent.length; i++) {
children.put(i, new ArrayList<>());
}
// 构建树的孩子列表
for (int i = 1; i < parent.length; i++) {
children.get(parent[i]).add(i);
}
}
public boolean lock(int num, int user) {
if (!locks.containsKey(num)) {
locks.put(num, user);
return true;
} else {
return false;
}
}
public boolean unlock(int num, int user) {
if (locks.containsKey(num) && locks.get(num) == user) {
locks.remove(num);
return true;
} else {
return false;
}
}
public boolean upgrade(int num, int user) {
if (!locks.containsKey(num) && hasLockedDescendant(num) && !hasLockedAncestor(num)) {
locks.put(num, user);
unlockDescendants(num);
return true;
} else {
return false;
}
}
private boolean hasLockedDescendant(int num) {
for (int child : children.get(num)) {
if (locks.containsKey(child) || hasLockedDescendant(child)) {
return true;
}
}
return false;
}
private boolean hasLockedAncestor(int num) {
int curr = parent[num];
while (curr != -1) {
if (locks.containsKey(curr)) {
return true;
}
curr = parent[curr];
}
return false;
}
private void unlockDescendants(int num) {
for (int child : children.get(num)) {
locks.remove(child);
unlockDescendants(child);
}
}
}
复杂度分析:
时间复杂度:
空间复杂度:
综上所述,整个 LockingTree 类的时间复杂度主要取决于构造函数的 O(n)、upgrade() 方法的 O(h + s) 和 hasLockedDescendant() 方法的 O(s)。空间复杂度主要取决于构造函数的 O(n) 和 upgrade() 方法的 O(h)。在一般情况下,树的高度 h 相对于节点数目 n 和子孙节点数目 s 较小。
因此,整体的时间复杂度为 O(n),空间复杂度为 O(n)。
LeetCode运行结果:
class LockingTree {
public LockingTree(int[] parent) {
int n = parent.length;
this.parent = parent;
lockUser = new int[n];
map = new HashMap<>();
for(int i = -1; i < n; i++){map.put(i,new HashSet<>());}
for(int i = 0; i < n; i++){map.get(parent[i]).add(i);}// 把当前节点放到对应的父节点里面
}
int[] parent;
int[] lockUser;
Map> map;
public boolean lock(int num, int user) {
if(lockUser[num] == 0){
lockUser[num] = user;
return true;
}
return false;
}
public boolean unlock(int num, int user) {
if(lockUser[num] == user){
lockUser[num] = 0;
return true;
}
return false;
}
public boolean upgrade(int num, int user) {
// 判断本身是否上锁
if (lockUser[num] != 0) {
return false;
}
// 判断祖先结点是否上锁
int find = parent[num];
while (find != -1) {
if (lockUser[find] != 0) {
return false;
}
find = parent[find];
}
// 判断是否有子孙节点上锁并解锁
if (hasLockedDescendants(num)) {
unlockDescendants(num);
lockUser[num] = user;
return true;
} else {
return false;
}
}
private boolean hasLockedDescendants(int num) {
for (int child : map.get(num)) {
if (lockUser[child] != 0 || hasLockedDescendants(child)) {
return true;
}
}
return false;
}
private void unlockDescendants(int num) {
for (int child : map.get(num)) {
if (lockUser[child] != 0) {
lockUser[child] = 0;
}
unlockDescendants(child);
}
}
}
这段代码实现的是一个锁定树的类 LockingTree,其中的思路如下:
总体来说,这段代码通过维护一个树结构和相关的数据结构数组,实现了对树中节点的锁定、解锁和升级操作。其中,锁定节点时需要考虑节点的锁定状态以及祖先节点和子孙节点的锁定情况。解锁节点时需要判断节点的锁定用户是否为当前用户。升级节点时需要判断节点的锁定状态、祖先节点的锁定状态以及子孙节点的锁定状态,并进行相应的操作。
复杂度分析:
总体来说,构造函数的时间复杂度为 O(N),其他方法的时间复杂度大多为 O(1),只有在遍历节点时需要递归,时间复杂度为 O(N)。空间复杂度方面,需要额外存储树结构、父节点数组和每个节点的锁定用户数组,因此为 O(N)。
LeetCode运行结果:
31. 下一个排列 - 力扣(LeetCode)
我们可以使用以下步骤来找到给定数组的下一个排列:
public class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
// 从后向前找到第一个升序的位置 i
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
// 在位置 i 后面找到刚好大于 nums[i] 的元素,并交换它们
int j = n - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
// 将 i 后面的元素按升序排列,以得到字典序最小的排列
reverse(nums, i + 1, n - 1);
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
swap(nums, start, end);
start++;
end--;
}
}
}
复杂度分析:
LeetCode运行结果:
以下是该方法的详细步骤:
public class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
int i = n - 2;
// 找到第一个满足 nums[i] < nums[i+1] 的元素下标 i
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
// 在位置 i 后面的子数组中,找到大于 nums[i] 且最接近的元素下标 j
int j = findNextGreater(nums, i);
// 交换 nums[i] 和 nums[j]
swap(nums, i, j);
}
// 对位置 i 后面的子数组进行递归调用,将其进行升序排序
reverse(nums, i + 1, n - 1);
}
private int findNextGreater(int[] nums, int start) {
int target = nums[start];
int nextGreater = start + 1;
while (nextGreater < nums.length && nums[nextGreater] > target) {
nextGreater++;
}
return nextGreater - 1;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
swap(nums, start, end);
start++;
end--;
}
}
}
复杂度分析:
reverse
需要对位置 i 后面的子数组进行升序排序,其时间复杂度为 O(n)。因此,总的时间复杂度为 O(n+n) = O(n)。LeetCode运行结果:
可以使用字典序法来找到给定数组的下一个排列。
以下是字典序法的实现步骤:
通过以上步骤,我们可以得到给定数组的下一个排列。
public class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
int i = n - 2;
// 找到第一个满足 nums[i] < nums[i+1] 的元素下标 i
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
// 从右往左找到第一个大于 nums[i] 的元素下标 j
int j = n - 1;
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
// 交换 nums[i] 和 nums[j]
swap(nums, i, j);
}
// 将位置 i 后面的子数组按升序排列
Arrays.sort(nums, i + 1, n);
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
复杂度分析:
时间复杂度:
空间复杂度:
LeetCode运行结果: