算法日记 - Day5

轮转数组

  • 这个思路比较简单,就是两次局部翻转 + 全局翻转
  • 不过需要注意如果 k >= nums.length,是需要取余的
java 复制代码
public void rotate(int[] nums, int k) {
    int n = nums.length;
    k = k % n;
    if (k == 0) return;
    reverse(nums, 0, n - k - 1);
    reverse(nums, n - k, n - 1);
    reverse(nums, 0, n - 1);
}

private void reverse(int[] nums, int from, int to) {
    while (from < to) {
        int temp = nums[from];
        nums[from++] = nums[to];
        nums[to--] = temp;
    }
}

除了自身以外数组的乘积

如果用除法,就所有元素乘积,最后遍历每个元素一除,时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

如果使用暴力,就是从前到后遍历,一个一个乘起来,跳过自己,时间复杂度是 O ( n ) O(n) O(n),然后一共 n n n 个元素,所以是 O ( n 2 ) O(n^2) O(n2) 时间复杂度,复杂度提高在哪里呢?是因为每次算的时候实际是有重复的,每个位置的 nums[i] 都被计算使用了 ( n − 1 ) (n - 1) (n−1) 次,能不能变为一次呢?

怎么减少重复呢?前缀和!不过不是计算和,而是计算乘积。但是它不是计算前缀和,还要跳过一个数,还不能除法。前缀和 + 后缀和不就可以了

java 复制代码
public int[] productExceptSelf(int[] nums) {
    // 前缀和 + 后缀和
    int[] answer = new int[nums.length];
    Arrays.fill(answer, 1);
    int pre = 1;
    // 先计算每个位置处 [0, i - 1] 的前缀乘积
    for (int i = 0; i < nums.length - 1; ++i) {
        pre *= nums[i];
        answer[i + 1] *= pre;
    }
    int back = 1;
    // 再加上每个位置处 [i + 1, nums.length - 1] 的后缀乘积
    for (int j = nums.length - 1; j > 0; --j) {
        back *= nums[j];
        answer[j - 1] *= back;
    }
    return answer;
}

矩阵置零

  • 能不能在遍历到 0 的时候把数据上下全置为 0?不可以,如果置为 0,后续你不知道遍历到的 0 是因为它本身是 0 还是你自己设置为 0 的,所以我们需要保留需要置为 0 的行或者列,最后遍历完之后统一清理。
java 复制代码
class Solution {
    public void setZeroes(int[][] matrix) {
        int n = matrix.length, m = matrix[0].length;
        boolean[] rowZero = new boolean[n]; // 行是否需要置零
        boolean[] colZero = new boolean[m]; // 列是否需要置零

        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (matrix[i][j] == 0) 
                    rowZero[i] = colZero[j] = true;
            

        for (int i = 0; i < n; ++i)
            if (rowZero[i])
                for (int j = 0; j < m; ++j)
                    matrix[i][j] = 0;

        for (int j = 0; j < m; ++j)
            if (colZero[j])
                for (int i = 0; i < n; ++i)
                    matrix[i][j] = 0;
    }
}

时间复杂度是 O ( m n ) O(mn) O(mn),这个是没办法优化的,空间复杂度是 O ( m + n ) O(m + n) O(m+n),还能优化吗?能,有一种用常量空间的解决方案

  • 用矩阵第一行保存该列是否被置零
  • 用矩阵第一列保存该行是否被置零

哎,为什么能这样呢?你想第一行的如果某个元素它本身是 0,你是不是这一列一定是要被清空的,或者这一列它本身是有 0 的,那最后一定也会被置零的,我放到第一行该列的位置,记录为 0 没问题吧

但是还有个小问题,需要区分第一行的 0 是本身这个位置就是 0,还是后来置为 0 的,因为我们是用的第一行第一列来存的,所以后续根据这个置为 0 的时候,是先把 matrix[1][1] 右下的位置都根据规则置为空后,再单独处理第一行第一列,假如原来第一行的 0 是他本身就是 0,那我们后续这一行都要清空的,否则就不用操作了 。所以还需要两个标志位。

java 复制代码
class Solution {
    public void setZeroes(int[][] matrix) {
        int n = matrix.length, m = matrix[0].length;
        boolean rowZero = false, colZero = false;
        for (int i = 0; i < n; i++)
            if (matrix[i][0] == 0)
                colZero = true;
        for (int j = 0; j < m; j++)
            if (matrix[0][j] == 0)
                rowZero = true; // 记录第一行第一列最后是否需要清空
        
        for (int i = 1; i < n; ++i)
            for (int j = 1; j < m; ++j)
                if (matrix[i][j] == 0) 
                    matrix[i][0] = matrix[0][j] = 0; // 遍历
            

        for (int i = 1; i < n; ++i)
            if (matrix[i][0] == 0)
                for (int j = 1; j < m; ++j)
                    matrix[i][j] = 0;

        for (int j = 1; j < m; ++j)
            if (matrix[0][j] == 0)
                for (int i = 1; i < n; ++i)
                    matrix[i][j] = 0; // 根据第一行第一列清空 matrix[1][1] 右下部分矩阵
        if (rowZero)
            for (int j = 0; j < m; ++j)
                matrix[0][j] = 0;
        if (colZero)
            for (int i = 0; i < n; ++i)
                matrix[i][0] = 0; // 处理第一行第一列
                
    }
}

相交链表

如果相交,那从后往前,肯定是找到交点的,并且这一部分是他们都有的,但是链表从前往后我们怎么找呢?观察示例 1, A A A 和 B B B 链表如果有交点,只可能从他们尾部长度相等的时候开始,也就是 A A A 第一个结点为 4 4 4 的位置, B B B 第一个结点为 6 6 6 的位置,如果他们 A.next == B.next,才会出现交点。所以我们可以先找链表长度,让他们都从尾部往前开始对齐,再往后找交点

java 复制代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA = 0, lenB = 0;
        ListNode A = headA, B = headB;
        // 计算A、B链表长度
        while (A != null) {
            lenA++;
            A = A.next;
        }

        while (B != null) {
            lenB++;
            B = B.next;
        }

        A = headA;
        B = headB;
        // 长度对齐
        if (lenA < lenB) {
            while (lenA != lenB) {
                B = B.next;
                lenB--;
            }
        } else {
            while (lenA != lenB) {
                A = A.next;
                lenA--;
            }
        }
		// 此时 A 往后的链表长度等于 B 往后的链表长度
        while (A != B) {
            A = A.next;
            B = B.next;
        }

        return A;
    }
}

下面再看一种更好的解法

假设有交点,从交点到结束长度为 z z z, A A A 头节点到交点长度为 x x x, B B B 头节点到交点长度为 y y y,有等式 x + y + z = = x + y + z x + y + z == x + y + z x+y+z==x+y+z,含义是什么呢?我让 p p p 从 headA \text{headA} headA 开始走,它遍历完 A A A 之后从 B B B 开始,让 q q q 从 headB \text{headB} headB 开始走,它遍历完 B B B 之后从 A A A 开始,如果有交点,他们一定会相遇!!!如果没有交点,最后两个人都会为 null

java 复制代码
class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p = headA;
        ListNode q = headB;
        while (p != q) {
            p = p != null ? p.next : headB;
            q = q != null ? q.next : headA;
        }
        return p;
    }
}
相关推荐
掉鱼的猫1 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·serverless
Accerlator1 小时前
RAG 评测
算法
学者猫头鹰1 小时前
对象存储:MinIO / 阿里云OSS
java
码哥DFS1 小时前
算法练习day1-备战2027届秋招
前端·javascript·数据结构·算法
程序员-Benothing2 小时前
Java集合:List实现类深度对比
java·开发语言·后端·面试·list
wabs6662 小时前
关于图论【卡码网108.多余的边的思考】
数据结构·算法·图论
wyjcxyyy2 小时前
若依框架漏洞总结
java·ctf
水无痕simon2 小时前
1 银行业务之个人存款业务
java
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-37 整数分解为若干项之和(C语言实现)
c语言·开发语言·数据结构·算法