怒刷LeetCode的第13天(Java版)

news2024/10/7 8:31:30

目录

第一题

题目来源

题目内容

解决方法

方法一:滑动窗口

方法二:哈希表和双指针

方法三:动态规划

第二题

题目来源

题目内容

解决方法

方法一:深度优先搜索(DFS)

方法二:树结构

第三题

题目来源

题目内容

解决方法

方法一:遍历交换

方法二:递归

方法三:字典序法


第一题

题目来源

30. 串联所有单词的子串 - 力扣(LeetCode)

题目内容

解决方法

方法一:滑动窗口

  1. 首先,统计words数组中每个单词的出现次数,并存储在wordCount中。
  2. 然后,通过遍历字符串s的所有可能起始位置,以总长度为totalLength进行滑动窗口操作。在每个窗口中,使用临时的tempCount和count变量来记录窗口内的单词出现次数。如果窗口内的单词出现次数符合要求,在结果列表中添加当前起始位置。
  3. 最后,返回结果列表。
import java.util.*;

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> 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<String, Integer> 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<String, Integer> 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;
    }
}

复杂度分析:

时间复杂度:

  • 首先需要遍历字符串s的所有可能起始位置,此操作的时间复杂度为O(N),其中N是s的长度。
  • 在每个起始位置,需要遍历words数组,对于每个单词,需要使用s.substring()方法获取指定位置的子串,并进行比较。此操作的时间复杂度为O(M*L),其中M是words数组的长度,L是单词的平均长度。
  • 因此,整体的时间复杂度为O(NML)。

空间复杂度:

  • 需要使用一个哈希表来存储words数组中每个单词的出现次数,以及临时变量存储计数信息。哈希表的大小取决于words数组的长度M。
  • 此外,还需要一个结果列表来存储符合条件的起始索引。
  • 因此,整体的空间复杂度为O(M)。

LeetCode运行结果:

方法二:哈希表和双指针

除了使用滑动窗口的方法外,还可以使用基于哈希表和双指针的解决方案。

这个解决方案使用了双指针的方法。

  • 首先,统计words数组中每个单词的出现次数,并存储在wordCount中。
  • 然后,通过遍历字符串s的所有可能起始位置,将左边界left初始化为当前起始位置。在每次循环中,使用右指针left + wordLength获取一个单词,并在哈希表tempCount中查找该单词是否存在且次数大于零。如果满足条件,则更新哈希表和计数,并将左边界右移一个单词长度。如果不满足条件,则结束循环。
  • 最后,如果计数等于0,则表示窗口内的单词都符合要求,将起始位置添加到结果列表中。
import java.util.*;

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> 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<String, Integer> 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<String, Integer> 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;
    }
}

复杂度分析:

时间复杂度:

  • 首先,需要遍历字符串s的所有可能起始位置,此操作的时间复杂度为O(N),其中N是s的长度。
  • 在每个起始位置,需要通过双指针循环遍历,直到窗口内的单词数量等于words数组的长度。由于窗口长度为words数组的长度乘以单词的长度,因此该操作的时间复杂度为O(L * M),其中L是单词的平均长度,M是words数组的长度。
  • 因此,整体的时间复杂度为O(N * L * M)。

空间复杂度:

  • 需要使用一个哈希表wordCount来存储words数组中每个单词的出现次数,该哈希表大小最多为words数组的长度M。
  • 此外,还需要使用一个哈希表tempCount来存储临时的单词计数信息,该哈希表的大小也为M。
  • 因此,整体的空间复杂度为O(M)。

综上所述,该基于哈希表和双指针的解决方案的时间复杂度为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<Integer> findSubstring(String s, String[] words) {
        List<Integer> 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<String, Integer> 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<String, Integer> 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数组中的所有单词,将该起始位置加入结果列表。

最后,返回结果列表即可。

复杂度分析:

  • 时间复杂度:假设字符串s的长度为n,words数组中单词的平均长度为m,words数组的长度为k。构建wordCountMap的时间复杂度为O(k),外层循环需要遍历长度为(n-windowLen+1)的窗口,其中windowLen为wordLen * wordCount,因此外层循环的时间复杂度为O(n)。内层循环遍历窗口内的所有单词,单次循环操作的时间复杂度为O(m),内层循环的总时间复杂度为O(wordCount * m)。因此,整体时间复杂度为O(n * k * m)。
  • 空间复杂度:构建wordCountMap和tempCountMap时使用了两个哈希表,每个哈希表的大小取决于words数组的长度,因此空间复杂度为O(k)。

总结起来,该动态规划解决方案的时间复杂度为O(n * k * m),空间复杂度为O(k)。其中,n为字符串s的长度,k为words数组的长度,m为单词的平均长度。

LeetCode运行结果:

第二题

题目来源

1993. 树上的操作 - 力扣(LeetCode)

题目内容

解决方法

方法一:深度优先搜索(DFS)

使用深度优先搜索(DFS)来实现 LockingTree 类。可以通过递归地遍历树的节点来判断锁的状态,以及执行相应的锁定、解锁和升级操作。

具体思路如下:

  1. 使用一个数组 parent 来表示树的父节点关系。
  2. 使用哈希表 locks 来保存节点的锁状态,其中键为节点编号,值为锁住该节点的用户编号。
  3. 使用哈希表 children 来保存每个节点的孩子节点列表。
  4. 在 lock() 方法中,判断给定节点是否已经上锁,如果未上锁则将其加锁,并返回 true;否则返回 false。
  5. 在 unlock() 方法中,判断给定节点是否已经被指定用户锁住,如果是则解锁,并返回 true;否则返回 false。
  6. 在 upgrade() 方法中,首先判断给定节点是否未上锁、有上锁的子孙节点以及没有任何上锁的祖先节点。如果满足条件,则将该节点上锁,并解锁所有子孙节点,然后返回 true;否则返回 false。
  7. 为了判断是否有上锁的子孙节点,可以使用递归函数 hasLockedDescendant() 来遍历给定节点的子孙节点。如果找到任何一个上锁节点,则返回 true;否则返回 false。
  8. 为了判断是否有上锁的祖先节点,可以使用迭代函数 hasLockedAncestor() 来遍历给定节点的父节点链。如果找到任何一个上锁节点,则返回 true;否则返回 false。
  9. 为了解锁给定节点的所有子孙节点,可以使用递归函数 unlockDescendants() 来遍历给定节点的子孙节点,并解锁它们。
  10. 在构造函数中,根据父节点关系构建每个节点的孩子节点列表。
  11. 上述步骤完成后,即可实现 LockingTree 类的相关操作。
import java.util.*;

class LockingTree {
    private int[] parent;
    private Map<Integer, Integer> locks;
    private Map<Integer, List<Integer>> 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);
        }
    }
}

复杂度分析:

时间复杂度:

  • 构造函数的时间复杂度为 O(n),其中 n 表示节点数目。需要遍历父节点数组构建每个节点的孩子节点列表。
  • lock() 方法的时间复杂度为 O(1)。只需在哈希表中执行一次插入操作。
  • unlock() 方法的时间复杂度为 O(1)。只需在哈希表中执行一次删除操作。
  • upgrade() 方法的时间复杂度为 O(h + s),其中 h 表示树的高度,s 表示子孙节点数目。需要判断给定节点是否未上锁、是否存在上锁的子孙节点以及是否存在上锁的祖先节点。遍历给定节点的子孙节点时间复杂度为 O(s),遍历给定节点的父节点链时间复杂度为 O(h)。
  • hasLockedDescendant() 方法的时间复杂度为 O(s),其中 s 表示子孙节点数目。需要递归判断给定节点的子孙节点是否被上锁。
  • hasLockedAncestor() 方法的时间复杂度为 O(h),其中 h 表示树的高度。需要迭代判断给定节点的父节点链中是否存在已上锁的节点。
  • unlockDescendants() 方法的时间复杂度为 O(s),其中 s 表示子孙节点数目。需要递归解锁给定节点的所有子孙节点。

空间复杂度:

  • 构造函数的空间复杂度为 O(n),需要存储每个节点以及其对应的孩子节点列表。
  • lock() 方法和 unlock() 方法的空间复杂度为 O(1),只需在哈希表中存储节点的上锁状态。
  • upgrade() 方法的空间复杂度为 O(h),需要存储给定节点的父节点链。
  • hasLockedDescendant() 方法的空间复杂度为 O(s),其中 s 表示子孙节点数目。递归调用时会使用函数调用栈来保存递归的上下文信息。
  • hasLockedAncestor() 方法的空间复杂度为 O(1),不需要额外的空间。
  • unlockDescendants() 方法的空间复杂度为 O(s),其中 s 表示子孙节点数目。递归调用时会使用函数调用栈来保存递归的上下文信息。

综上所述,整个 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<Integer,Set<Integer>> 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,其中的思路如下:

  • 构造函数 LockingTree(int[] parent):接收一个父节点数组,表示树的结构。在构造函数中,首先初始化一些变量,包括父节点数组、每个节点的锁定用户数组和节点与子节点的映射关系。然后,遍历父节点数组,将每个节点放入对应父节点的子节点集合。
  • 锁定节点方法 lock(int num, int user):接收一个节点编号和用户编号,用于将指定节点锁定给指定用户。在该方法中,首先判断该节点是否已被锁定,如果未被锁定,则将其锁定给指定用户并返回 true,表示成功锁定;如果已被锁定,则返回 false,表示锁定失败。
  • 解锁节点方法 unlock(int num, int user):接收一个节点编号和用户编号,用于解除指定节点的锁定状态。在该方法中,首先判断该节点是否由指定用户锁定,如果是则解除锁定并返回 true,表示成功解锁;如果不是,则返回 false,表示解锁失败。
  • 升级节点方法 upgrade(int num, int user):接收一个节点编号和用户编号,用于升级指定节点的锁定状态。在该方法中,首先判断该节点是否已被锁定,如果是则返回 false,表示升级失败;然后,逐级判断该节点的祖先节点是否有被锁定的节点,如果有则返回 false,表示升级失败;接着,判断该节点的子孙节点是否有被锁定的节点,如果有,则解锁所有子孙节点并将当前节点锁定给指定用户,并返回 true,表示升级成功;如果没有,则返回 false,表示升级失败。
  • 辅助方法 hasLockedDescendants(int num):递归地检查某个节点的子孙节点中是否有被锁定的节点。在该方法中,首先遍历该节点的所有子节点,如果有子节点已被锁定,或者子节点的子孙节点中有被锁定的节点,则返回 true,表示存在已被锁定的节点;如果遍历完所有子节点,都没有找到已被锁定的节点,则返回 false,表示没有已被锁定的节点。
  • 辅助方法 unlockDescendants(int num):递归地解锁某个节点的所有子孙节点。在该方法中,首先遍历该节点的所有子节点,如果子节点已被锁定,则解除锁定;然后,递归调用该方法,解锁子节点的所有子孙节点。

总体来说,这段代码通过维护一个树结构和相关的数据结构数组,实现了对树中节点的锁定、解锁和升级操作。其中,锁定节点时需要考虑节点的锁定状态以及祖先节点和子孙节点的锁定情况。解锁节点时需要判断节点的锁定用户是否为当前用户。升级节点时需要判断节点的锁定状态、祖先节点的锁定状态以及子孙节点的锁定状态,并进行相应的操作。

复杂度分析:

  • 构造函数 LockingTree(int[] parent):遍历父节点数组,构建树结构和相关数据结构数组。时间复杂度为 O(N),其中 N 是节点的数量。
  • 锁定节点方法 lock(int num, int user):检查节点的锁定状态并锁定节点。时间复杂度为 O(1)。
  • 解锁节点方法 unlock(int num, int user):检查节点的锁定状态并解锁节点。时间复杂度为 O(1)。
  • 升级节点方法 upgrade(int num, int user):检查节点的锁定状态、祖先节点的锁定状态以及子孙节点的锁定状态,并进行相应操作。最坏情况下,需要递归遍历所有的子孙节点,时间复杂度为 O(N)。
  • 辅助方法 hasLockedDescendants(int num):递归地检查某个节点的子孙节点中是否有被锁定的节点。最坏情况下,需要遍历所有的子孙节点,时间复杂度为 O(N)。
  • 辅助方法 unlockDescendants(int num):递归地解锁某个节点的所有子孙节点。最坏情况下,需要遍历所有的子孙节点,时间复杂度为 O(N)。

总体来说,构造函数的时间复杂度为 O(N),其他方法的时间复杂度大多为 O(1),只有在遍历节点时需要递归,时间复杂度为 O(N)。空间复杂度方面,需要额外存储树结构、父节点数组和每个节点的锁定用户数组,因此为 O(N)。

LeetCode运行结果:

第三题

题目来源

31. 下一个排列 - 力扣(LeetCode)

题目内容

解决方法

方法一:遍历交换

我们可以使用以下步骤来找到给定数组的下一个排列:

  1. 从右往左遍历数组,找到第一个下降的元素,记为 i。即找到满足 nums[i] < nums[i+1] 的最大下标 i。
  2. 如果找不到这样的 i,说明整个数组是降序的,即不存在下一个更大的排列。此时,将整个数组翻转,得到字典序最小的排列。
  3. 如果找到了 i,再次从右往左遍历数组,找到第一个大于 nums[i] 的元素,记为 j。即找到满足 nums[j] > nums[i] 的最大下标 j。
  4. 交换 nums[i] 和 nums[j]。
  5. 将位置 i 后面的元素按升序排列,以得到字典序最小的排列。
  6. 通过以上步骤,我们可以在原地修改给定数组,得到下一个排列。
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--;
        }
    }
}

复杂度分析:

  • 时间复杂度:O(n),其中 n 是数组的长度。我们需要从右往左遍历数组找到第一个下降的元素,这一步最坏情况下需要遍历整个数组。然后,我们需要再次从右往左遍历数组找到第一个大于 nums[i] 的元素。接下来,在位置 i 后面进行元素的升序排列,这一步也需要 O(n) 的时间复杂度。因此,总体上,算法的时间复杂度是 O(n)。
  • 空间复杂度:O(1)。算法只需要使用常数个额外的变量,所以空间复杂度是 O(1)。

LeetCode运行结果:

方法二:递归

以下是该方法的详细步骤:

  1. 从右往左找到第一个满足 nums[i] < nums[i+1] 的元素下标 i。如果找不到这样的 i,则说明整个数组是降序的,不存在下一个更大的排列。直接将整个数组进行翻转,得到字典序最小的排列,算法结束。
  2. 在位置 i 后面的子数组中,找到大于 nums[i] 且最接近的元素下标 j。
  3. 交换 nums[i] 和 nums[j]。
  4. 对位置 i 后面的子数组进行递归调用,将其进行升序排序。
  5. 通过以上步骤,我们可以得到给定数组的下一个排列。
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--;
        }
    }
}

复杂度分析:

  • 时间复杂度:在最坏情况下,需要遍历整个数组找到位置 i,并在位置 i 之后的子数组中查找元素 j,所以时间复杂度为 O(n)。并且递归调用函数 reverse 需要对位置 i 后面的子数组进行升序排序,其时间复杂度为 O(n)。因此,总的时间复杂度为 O(n+n) = O(n)。
  • 空间复杂度:除了输入数组以外,在递归调用的过程中不需要额外的空间,所以空间复杂度为 O(1)。

LeetCode运行结果:

方法三:字典序法

可以使用字典序法来找到给定数组的下一个排列。

以下是字典序法的实现步骤:

  1. 从右往左找到第一个满足 nums[i] < nums[i+1] 的元素下标 i。如果找不到这样的 i,则说明整个数组是降序的,不存在下一个更大的排列。直接将整个数组进行翻转,得到字典序最小的排列,算法结束。
  2. 从右往左找到第一个大于 nums[i] 的元素下标 j。
  3. 交换 nums[i] 和 nums[j]。
  4. 将位置 i 后面的子数组按升序排列。

通过以上步骤,我们可以得到给定数组的下一个排列。

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;
    }
}

复杂度分析:

时间复杂度:

  • 寻找第一个满足 nums[i] < nums[i+1] 的元素下标 i 的过程需要遍历整个数组,时间复杂度为 O(n)。
  • 从右往左找到第一个大于 nums[i] 的元素下标 j 的过程最坏情况下需要遍历整个数组,时间复杂度也为 O(n)。
  • 交换 nums[i] 和 nums[j] 只是一次简单的元素交换,时间复杂度为 O(1)。
  • 将位置 i 后面的子数组按升序排列的过程使用了 Arrays.sort() 方法,其时间复杂度为 O(mlogm),其中 m 是子数组的长度,因为只对部分数组进行排序,所以这里的时间复杂度可以近似为 O(n)。
  • 综上所述,算法的总体时间复杂度为 O(n)。

空间复杂度:

  • 算法只使用了常数级别的额外空间,故空间复杂度为 O(1)。

LeetCode运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1033786.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

rv1126-rv1109-RkLunch.sh

RkLunch.sh是个脚本,来自哪里 书接上回:rv1126-rv1109-瑞芯微的 IPC 程序_旋风旋风的博客-CSDN博客 修改之后 屏蔽了两行之后,就没有开机自启rklunch了: # [ -f /oem/RkLunch.sh ] && source /oem/RkLunch.sh 就是运行source /oem/RkLunch.sh 这里就跑了RkL…

基于微信小程序的驾校报名系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户微信小程序端的主要功能有&#xff1a;驾校教练的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码…

动手学深度学习(pytorch版)第二章-2.2数据预处理Note-pandas

1. 创建 import osos.makedirs(os.path.join(.., data), exist_okTrue) data_file os.path.join(.., data, house_tiny.csv) with open(data_file, w) as f:f.write(NumRooms,Alley,Price\n) # 列名f.write(NA,Pave,127500\n) # 每行表示一个数据样本f.write(2,NA,106000\…

平板用的触控笔什么牌子好?性价比高的触控笔推荐

随着平板电脑的普及&#xff0c;越来越多用户为了方便都选择了电容笔&#xff0c;电容笔已经完全代替了我们的手指&#xff0c;并且使我们的书写速度得到了极大的提升。然而&#xff0c;因为其的独特的重力压感功能与芯片技术&#xff0c;导致了原装笔的售价一直居高不下&#…

深入探析NCV7356D1R2G 单线CAN收发器各项参数

NCV7356D1R2G深力科是一款用于单线数据链路的物理层器件&#xff0c;能够使用多种具碰撞分解的载波感测多重存取 (CSMA/CR) 协议运行&#xff0c;如博世控制器区域网络 (CAN) 2.0 版。此串行数据链路网络适用于不需要高速数据的应用&#xff0c;低速数据可在物理介质部件和微处…

用selenium和xpath定位元素并获取属性值以及str字符型转json型

页面html如图所示&#xff1a; 要使用xpath定位这个div元素&#xff0c;并且获取其属性data-config的内容值。 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Optionshost127.0.0.1 port10808 …

线程安全,与多线程的应用

一、线程安全 1.什么是线程安全 2.用程序模拟线程安全问题 public class Account {private double money;//余额private String cardId;//卡号public Account() {}public Account(double money, String cardId) {this.money money;this.cardId cardId;}public void drawMone…

LeetCode 1993. 树上的操作:大模拟

【LetMeFly】1993.树上的操作&#xff1a;大模拟 力扣题目链接&#xff1a;https://leetcode.cn/problems/operations-on-tree/ 给你一棵 n 个节点的树&#xff0c;编号从 0 到 n - 1 &#xff0c;以父节点数组 parent 的形式给出&#xff0c;其中 parent[i] 是第 i 个节点的…

基于微信小程序的健康评估系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户微信端的主要功能有&#xff1a;医生微信端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取…

iOS17正式版BUG汇总:无法正常拨打电话、小组件不可用、无线充电不可用等问题

今天凌晨 iOS 17 正式版发布&#xff0c;相信不少尝鲜派已经更新体验了iOS17的新功能了&#xff0c;但还有很多用户选择观望看是否要升级&#xff0c;小编汇总了目前已更新的用户反馈的已知BUG&#xff0c;供大家查看是否要更新iOS17正式版&#xff01; 目前已知BUG&#xff1…

【LeetCode75】第六十二题 多米诺和托米诺平铺

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我一个数字n&#xff0c;表示我们有2*n大小的地板需要铺。 我们拥有两种瓷砖&#xff0c;一种的长度为2的多米诺&#xff0c;另一…

Jetpack Compose干货,如何让Compose Dialog从屏幕任意方向进入

一、前言 来个效果图&#xff0c;基于Compose Dialog&#xff0c;最终要实现的库能力如下&#xff1a; 这里使用的是这个包下面的&#xff1a; androidx.compose.ui.window.Dialog androidx.compose.material3.AlertDialog它内部调用的也是androidx.compose.ui.window.Dialog …

Centos7 安装部署 Kubernetes(k8s) 高可用集群

1&#xff1a;基础环境准备 宿主机系统集群角色服务器IP主机名称容器centos7.6master192.168.2.150ks-m1dockercentos7.6master192.168.2.151ks-n1dockercentos7.6master192.168.2.152ks-n2docker 1.1 服务器初始化及网络配置 VMware安装Centos7并初始化网络使外部可以访问*…

No2.详解【2023年全国大学生数学建模竞赛】C题——蔬菜类商品的自动定价与补货决策(代码 + 详细输出 + 数据集代码 下载)

只有不回避痛苦和迷茫的人,才有资格去谈乐观和坚定。命运不会厚待谁,悲喜也不会单为你准备。 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌟[3] 2022年度博客之星人工智能…

爬虫获取接口数据

上一讲讲的是获取静态网页数据的教程&#xff0c;适用于我们要爬取的数据在网页源代码中出现&#xff0c;但是还是有很多的数据是源代码中没有的&#xff0c;需要通过接口访问服务器来获得&#xff0c;下面我就来讲讲如何爬取这类数据。 以巨潮资讯网爬取比亚迪企业年报为例。…

解决windows端口占用

WINR打开cmd窗口&#xff1b;输入命令查看哪个进程占用&#xff0c;8848为要查询占用的端口号&#xff1a;netstat -ano | findstr 8848&#xff1b; 3.杀死进程&#xff0c;输入taskkill /f /t /im 10672 其中10672为上面命令查出来的进程号。

24. 图论 - 图的表示种类

Hi&#xff0c;你好。我是茶桁。 之前的一节课中&#xff0c;我们了解了图的来由和构成&#xff0c;简单的理解了一下图的一些相关概念。那么这节课&#xff0c;我们要了解一下图的表示&#xff0c;种类。相应的&#xff0c;我们中间需要穿插一些新的知识点用于更好的去理解图…

Python异步编程并发执行爬虫任务,用回调函数解析响应

一、问题&#xff1a;当发送API请求&#xff0c;读写数据库任务较重时&#xff0c;程序运行效率急剧下降。 异步技术是Python编程中对提升性能非常重要的一项技术。在实际应用&#xff0c;经常面临对外发送网络请求&#xff0c;调用外部接口&#xff0c;或者不断更新数据库或文…

漏刻有时数据可视化Echarts组件开发(31):geomap伪3D配置示例

echarts.registerMap("丹东", getData());let data = getData().features.map((item) => {return {name: item.properties.name,};});const points = [[116.289929,40.265374],[116.754101,40.063877],[116.229504,39.764735],[115.883434,39.899721]]let option …

Unity的AB包相关

1、打包 在这个界面左边右键&#xff0c;CreateNewBundle 将要打包的模型制作成预设体 在下面勾选 选好平台路径&#xff0c;点击Build 2、加载AB包 public class ABTest : MonoBehaviour {// Start is called before the first frame updatevoid Start(){//加载AB包AssetB…