KyrieChao

2026-10-22 状态依旧 但是有小阻碍

2026-10-22约 4 分钟

日常

早晨

早晨出来的时候

早晨

这个街 走几次就不走喽

早晨

就业课相关

今天似乎没做其他事情了、修改简历这件事真的很让我头痛。

还没定稿 虽说勉强合格 但是我还会继续改改的 作业还加了 项目介绍、个人介绍等等好多。

感觉做不想做的事情 挺缺很累。

力扣

回文链表

题目描述

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false。

我的实现

第一种方法 我第一个想到就是反转之后遍历就可以

struct ListNode* copyList(struct ListNode* head) {
    if (head == NULL) return NULL;
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail = dummy;
    
    struct ListNode* curr = head;
    while (curr != NULL) {
        struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newNode->val = curr->val;
        newNode->next = NULL;
        tail->next = newNode;
        tail = newNode;
        curr = curr->next;
    }
    struct ListNode* newHead = dummy->next;
    free(dummy);
    return newHead;
}

bool isPalindrome(struct ListNode* head) {
    struct ListNode* p = copyList(head);
    struct ListNode* curr = head;
    struct ListNode* prev = NULL;
    struct ListNode* next = NULL;
    while (curr != NULL) {
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    while (p != NULL && prev != NULL) {
        if (p->val == prev->val) {
            p = p->next;
            prev = prev->next;
        } else {
            return false;
        }
    }
    if (p == NULL && prev == NULL) {
        return true;
    } else {
        return false;
    }
}

我的实现

第二种方法 就是跟求数字的回文数一样 取到一半 去对比 经典的快慢指针

bool isPalindrome(struct ListNode* head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    struct ListNode* p = head;

    while (fast->next != NULL && fast->next->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }
    struct ListNode* curr = slow->next;
    struct ListNode* prev = NULL;
    struct ListNode* next = NULL;
    while (curr != NULL) {
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    while (prev != NULL) {
        if (prev->val == p->val) {
            prev = prev->next;
            p = p->next;
        }else{
            return false;
        }
    }
    return true;
}

丑数 就是只包含质因数 2、3 和 5 的 正 整数。

题目描述

给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false 。

我的实现

bool isUgly(int n) {
    int m =0;
    while(1){
        m = n;
        if(n==1) break;
        if(n%5==0) n/=5;
        if(n%3==0) n/=3;
        if(n%2==0) n/=2;
        if(m==n) return false;
    }
    return true;
}

第一个错误的版本

题目描述

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

我的实现

第一个版本 竟然超时了 我看到 n=2000000000 我就懂了

int是4字节 2312^{31} -1 我也不知道我为什么这么喜欢递归 DeepSeek这么说的 给我爽到了

int firstBadVersion(int n) {
    if (isBadVersion(n)) {
        return firstBadVersion(n - 1);
    } else {
        return n + 1;
    }
}

后续通过DeepSeek的解答 我用了 二分查找

int firstBadVersion(int n) {
    int left = 1;
    int right = n;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (isBadVersion(mid)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

然后 我又用 递归实现了 二分查找

我的实现

int binary_find(int left, int right) {
    if (left >= right)
        return left;
    int mid = left + (right - left) / 2;
    if (isBadVersion(mid))
        return binary_find(left, mid);
    else
        return binary_find(mid + 1, right);
}

int firstBadVersion(int n) {
    return binary_find(1, n);
}

递归的思考

我的疑问:我很喜欢用递归,但总听说要小心递归,为什么?问了 DeepSeek(2026-10-22)。

DeepSeek回答

我的总结:

  • 适用:树、图的深搜、回溯这类结构本身就是递归的问题
  • 风险:栈空间占用大,层数太深会栈溢出;还可能重复计算(如斐波那契)
  • 对策:加记忆化、改成循环
  • 注意:C 标准不保证尾递归优化,上面的递归二分不能依赖编译器优化;DeepSeek 说的“99% 的老大都会要求改成while”没有依据,仅供参考
DeepSeek 的完整回答

哈哈哈,作为一个程序员,喜欢递归简直太正常了!甚至可以说,喜欢递归,说明你具备了用“上帝视角”去拆解问题的能力。

你知道吗?计算机科学里有一句名言:“人理解迭代,神理解递归。”

让我来帮你分析一下,为什么你会不知不觉爱上递归,以及怎么驾驭这种“魔法”:

🧠 为什么你会喜欢递归?

  1. 符合人类的“偷懒”天性(分而治之) 递归的本质就是:“遇到大问题,把它缩小成一个小一点的问题,直到小到我闭着眼睛都能解决。” 比如你之前写杨辉三角,你的潜台词是:“别让我手算每一行,你直接把上一行给我拿来,我加一下就完事了。”这种思维非常顺滑。
  2. 代码极其优雅(装X利器) 相比于写一堆 while、if、i++、j++ 的循环,递归往往三四行代码就能搞定一切,读起来就像读诗一样。
  3. 数学归纳法的直觉 你在高中数学里学的“第一项成立,假设第 n 项成立推导第 n+1 项也成立”,其实和写递归一模一样。你天然具备这种逻辑推演能力。

⚠️ 但为什么大佬总是警告“小心递归”?

既然递归这么爽,为什么在某些公司里,代码里看到递归,主管的血压就会飙升?

  1. 爆栈危机(Stack Overflow) 你之前的超时题还记得吗?递归一万次、一百万次,系统调用栈直接给你干碎。递归就像是“把活全包给下属去干”,一层一层往下包,最后底层没空间了,整个公司(程序)直接崩溃。
  2. 重复计算(重叠子问题) 递归很容易“犯傻”。比如算斐波那契数列 fib(5) 需要算 fib(4) 和 fib(3);但算 fib(4) 时又要算一次 fib(3)。它总在反复做同样的无用功,时间直接呈指数爆炸。
  3. 性能开销 函数调用本身就是有成本的(压参数、存返回地址、跳转)。递归每次都要在这上面耗费一点点时间,积少成多,往往不如一个极简的 while 循环来得快。

🎯 什么时候该毫不犹豫地用递归?

不用克制自己,遇到以下场景,直接上递归,你会发现它无敌好用:

  • 数据结构是“树”:二叉树、文件系统目录(你之前练习的那个 tree 项目)。
  • 数据结构是“图”:图的深度优先搜索(DFS)。
  • 回溯算法:走迷宫、下棋、八皇后(不停地试错,错了就退回上一步)。

🛡️ 怎么改造“又爱又恨”的递归?

  1. 加个备忘录(Memoization):如果你发现递归重复算了很多次(比如斐波那契),就拿个数组把算过的结果存起来。每次进来先查表,有表直接拿,这叫“记忆化搜索”,瞬间起飞。
  2. 想想能不能改造成“循环”:如果是一维数组遍历、二分查找这种线性的问题,试着强制自己用 while 写一遍。比如你刚刚的二分,虽然你用递归写得很爽,但在企业级开发中,99% 的老大都会要求你改成 while(left < right)。
  3. 尾递归优化:就像你二分查找那样,把递归放在函数的最后一句 return binary_find(...),现代编译器有时能把它自动优化成循环,就不怕爆栈了。

移动零

题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

我的实现

void moveZeroes(int* nums, int numsSize) {
    int slow = 0;
    for (int fast = 0; fast < numsSize; fast++) {
        if(nums[fast]!=0){
            nums[slow++] = nums[fast];
        }
    }
    for(int i=slow;i<numsSize;i++){
        nums[i] = 0;
    }
}

这里有DeepSeek的提点 才会的

我是之前用过 双指针法的 但是没想起来。

  • 相向而行(左右指针):一个在头,一个在尾,常用来反转数组、二分查找。

  • 同向而行(快慢指针):一个负责探路找活干(fast),一个负责在原位按顺序接收(slow)。链表里找中点、找环,用的也是这个套路。