【算法】【学习笔记】递归

递归要注意边界条件和返回值。

❔题目

1. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

解题思路

首先可以看出边界条件是list1为null时,返回list2;list2为null时,返回list1。

然后对当前值进行判断,当list1的值大于list2时,让list1.next继续和list2进行合并,而不是两个一起切换为next,这样子就乱套了。

实现

function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null {
    if(list1 === null){
        return list2;
    }
    if(list2 === null){
        return list1;
    }
    
    if(list1.val <= list2.val){
        list1.next = mergeTwoLists(list1.next, list2);
        return list1;
    }else{
        list2.next = mergeTwoLists(list1, list2.next);
        return list2;
    }
};
public class Solution {
    public ListNode MergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null) return list2;
        if(list2 == null) return list1;

        if(list1.val <= list2.val){
            list1.next = MergeTwoLists(list1.next, list2);
            return list1;
        }else{
            list2.next = MergeTwoLists(list1, list2.next);
            return list2;
        }
    }
}
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr) return list2;
        if(list2 == nullptr) return list1;

        if(list1->val <= list2->val){
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }else{
            list2->next = mergeTwoLists(list1,  list2->next);
            return list2;
        }
    }
};

复杂度分析

  • 时间复杂度\(O(m+n)\)
    m、n 分别为两个链表的长度,最多处理两个链表中的每个节点一次。
  • 空间复杂度\(O(m+n)\)
    使用递归实现,递归调用栈最多达到 \(O(m+n)\) 层。

2. 找出第 N 个二进制字符串中的第 K 位

给你两个正整数 n 和 k,二进制字符串 \(S_n\) 的形成规则如下:

  • \(S_1\) = "0"
  • 当 i > 1 时,\(S_i\) = \(S_{i-1}\) + "1" + reverse(invert(\(S_{i-1}\)))
    其中 + 表示串联操作,reverse(x) 返回反转 x 后得到的字符串,而 invert(x) 则会翻转 x 中的每一位(0 变为 1,而 1 变为 0)。

例如,符合上述描述的序列的前 4 个字符串依次是:

  • S1 = "0"
  • S2 = "011"
  • S3 = "0111001"
  • S4 = "011100110110001"
    请你返回 \(S_n\) 的 第 k 位字符 ,题目数据保证 k 一定在 \(S_n\) 长度范围以内。

解题思路

这个题目需要用到分治的思想,假如直接递归构造S,会导致 \(2^n\) 的时间复杂度。

根据规律可以知道,中间的数必定为1。假如k在中间的左边,那么递归调查左半边,假如在右边,则递归调查右半边,并由于是右半边,右半边经过了 翻转 和 取反:

  • 反转位置映射:\(S_n\) 中的第 \(k\) 位,对应 \(S_{n-1}\) 中的第 \(2^n - k\) 位。
  • 取反值映射:找到 \(S_{n-1}\)\(2^n - k\) 位的值后,将其 0 变 1,1 变 0 即可。

实现

function findKthBit(n: number, k: number): string {
    // 递归基:S1 只有一位 '0'
    if (n === 1) return "0";

    // len = 2^n - 1, mid = 2^(n-1)
    const mid = 1 << (n - 1); 

    if (k === mid) {
        // 1. 正中间位置固定是 '1'
        return "1";
    } else if (k < mid) {
        // 2. 左半边:直接递归到 S_(n-1) 的第 k 位
        return findKthBit(n - 1, k);
    } else {
        // 3. 右半边:递归到 S_(n-1) 的第 (2^n - k) 位,并将结果取反
        const bit = findKthBit(n - 1, (1 << n) - k);
        return bit === '0' ? '1' : '0';
    }
}
public class Solution {
    public char FindKthBit(int n, int k) {
        if(n == 1) return '0';
        int mid = 1 << (n-1);

        if(k == mid){
            return '1';
        }else if(k < mid){
            return FindKthBit(n-1, k);
        }else{
            int bit = FindKthBit(n-1, (1 << n) - k);
            return bit == '1' ? '0' : '1';
        }
    }
}
class Solution {
public:
    char findKthBit(int n, int k) {
        if(n == 1) return '0';

        int mid = 1 << (n-1);

        if(k == mid){
            return '1';
        }else if(k < mid){
            return findKthBit(n-1, k);
        }else{
            return findKthBit(n-1, (1 << n) - k) == '1' ? '0' : '1';
        }
    }
};

复杂度分析

  • 时间复杂度\(O(N)\)
    每次递归只需要判断当前位置属于左半边、中间还是右半边,并将问题规模减 1,最多递归 N 层。
  • 空间复杂度\(O(N)\)
    递归调用栈最多需要 \(O(N)\) 层。

3. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。

测试用例保证输出的长度不会超过 \(10^5\)

解题思路

使用深度优先遍历,

  • 当扫描到数字时,记录数字 k ,要注意进位的计算;
  • 当扫描到[时,继续深度优先遍历,并且将结果重复 k 次,并将 k 置0;
  • 当扫描到]时,结束遍历,返回结果,并位置+1;
  • 当扫描到字母时,与结果直接拼接;

实现

function decodeString(s: string): string {
    let i = 0;

    function dfs(): string{
        let res = "";
        let k = 0;

        while(i < s.length){
            const char = s[i];

            if(char >= '0' && char <= '9'){
                // 遇到数字,累加计算 k,*10用于进位
                k = k * 10 + Number(char);
                i++;
            } else if (char === '[') {
                // 2. 遇到左括号:跳过 '[',开启递归去解内层
                i++;
                const sub = dfs(); // sub 就是内层括号解码出来的字符串
                res += sub.repeat(k); // 重复 k 次拼接到当前层
                k = 0; // 重置 k
            } else if (char === ']') {
                // 3. 遇到右括号:跳过 ']',说明当前层解码完毕,返回结果
                i++;
                return res;
            } else {
                // 4. 遇到普通字母:直接拼接
                res += char;
                i++;
            }
        }

        return res;
    }

    return dfs();
};
public class Solution {
    public string DecodeString(string s) {
        int i = 0;
        return dfs(ref i, ref s);
    }

    public string dfs(ref int i, ref string s){
        int k = 0;
        StringBuilder res = new StringBuilder();

        while(i < s.Length){
            if(s[i] >= '0' && s[i] <= '9'){
                k = k*10 + (s[i] - '0');
                i++;
            }else if(s[i] == '['){
                i++;
                string sub = dfs(ref i, ref s);
                for(int j = 0; j < k; j++){
                    res.Append(sub);
                }
                k = 0;
            }else if(s[i] == ']'){
                i++;
                return res.ToString();
            }else{
                res.Append(s[i]);
                i++;
            }
        }

        return res.ToString();
    }
}
class Solution {
public:
    string decodeString(string s) {
        int i = 0;
        return dfs(i, s);
    }

    string dfs(int& i, const string& s){
        int k = 0;
        string res = "";

        while(i < s.size()){
            if(s[i] >= '0' && s[i] <= '9'){
                k = k * 10 + (s[i] - '0');
                i++;
            }else if(s[i] == '['){
                i++;
                string cur = dfs(i, s);
                for(int j = 0; j < k; j++){
                    res += cur;
                }
                k = 0;
            }else if(s[i] == ']'){
                i++;
                return res;
            }else{
                res += s[i++];
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度\(O(N+M)\)
    深度优先遍历,遍历 s 的长度 \(N\) ,并且还有构造的结果的长度 \(M\)
  • 空间复杂度\(O(D)\)
    其中 \(D\) 为字符串的最大嵌套深度,最坏情况下为 \(O(N)\)

引用

力扣探险模式

注:本文为个人学习与刷题笔记,部分文本结构与排版格式由 AI 辅助整理。

posted @ 2026-08-14 14:00  SEHOD  阅读(4)  评论(0)    收藏  举报