22.括号生成
思路:一个包含n组括号的有效字符串,一定可以分为"(" + L + ")" + R
,其中L和R为包含“0~n-1"对括号的有效字符串,因此考虑递归。这里为什么不将其简单的分为两部分L + R
呢,是因为L和R都可能呢为0个括号的有效字符串,而递归需要缩小状态空间。若不每次分出一个括号,则L或R任一为空字符串(即包含0个括号的有效字符串)时,会陷入死循环。L+R中总共包含n-1个括号,每次手动添加一个括号。遍历各种情况。此时可采用缓存来保存之前的结果,因为之后会调用用到。
代码:
private ArrayList[] cache = new ArrayList[9];
public List<String> generateParenthesis(int n) {
if (cache[n] != null) {
return cache[n];
}
ArrayList<String> result = new ArrayList<>();
if (n == 0) {
result.add("");
} else {
for (int i = 0; i < n; i++) {
List<String> left = generateParenthesis(i);
List<String> right = generateParenthesis(n - 1 - i);
for (String l : left) {
for (String r : right) {
result.add("(" + l + ")" + r);
}
}
}
}
cache[n] = result;
return result;
}
时间复杂度:O( 4 n n \frac{4^n}{\sqrt{n}} n4n),这个复杂度取决于整体结果的状态空间大小,涉及到数学推导,有兴趣可查看卡特兰数
空间复杂度:O(n)
23.合并K个升序链表
思路:针对K个链表设立K个指针,每个指针始终指向每个链表当前尚未合并的最小元素。然后将这k个结点中最小的那个合并到结果中,同时将对应指针在对应的链表中推进一格。若推进到队尾,则不再推进。此时需要比较k个结点的大小,比较时间为O(K)。为优化效率,可使用优先队列(底层为小顶堆)来使得比较的时间复杂度优化为O(logK)。同时设立标志位记录已经被全部合并的链表数,当所有链表合并完成后,返回结果。(查看题解后,发现可以略去这个标志位,因为优先队列中元素的个数总是等同于当前未合并完成的链表数量)
代码:
private static class Entity {
int index;
ListNode value;
public Entity(int index, ListNode value) {
this.index = index;
this.value = value;
}
}
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
ListNode head = new ListNode(-1), ptr = head;
PriorityQueue<Entity> pq = new PriorityQueue<>(Comparator.comparingInt(o -> o.value.val));
int emptyList = 0;
for (int i = 0; i < lists.length; i++) {
if (lists[i] == null) {
emptyList ++;
} else {
pq.add(new Entity(i, lists[i]));
}
}
while (emptyList < lists.length) {
Entity entity = pq.poll();
ptr.next = entity.value;
ptr = ptr.next;
if (entity.value.next == null) {
emptyList ++;
} else {
entity.value = entity.value.next;
pq.add(entity);
}
}
return head.next;
}
时间复杂度:O(nlogk),此处n为所有链表中的结点数量和,因为优先队列中最多有k个元素,因此插入与弹出的时间复杂度都为O(logk),所有结点都要插入和弹出一次。
空间复杂度:O(k)
24.两两交换链表中的结点
思路:此时不允许修改结点的值,即需要在原链表中进行交换。每次交换需要链表中有至少两个结点还未完成交换,称第一个结点为first,第二个结点为second。交换过程中还需要涉及到first的前一个结点,因此采用三个指针指向这三个结点。交换时注意结点中next指针的更改顺序,避免丢失信息。交换完成后,更新指针并进行下一次交换,直到少于两个结点还未完成交换时,整个链表的交换完成。
代码:
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = new ListNode(-1, head), ptr = newHead;
while (ptr.next != null && ptr.next.next != null) {
ListNode first = ptr.next, second = first.next;
first.next = second.next;
second.next = first;
ptr.next = second;
ptr = first;
}
return newHead.next;
}
时间复杂度:O(n)
空间复杂度:O(1)