算法日记 - 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;
    }
}
相关推荐
benbenAItalk8 小时前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
sunshine22 girl8 小时前
Java学习二 基本语法2, 算术运算符
java·学习
2601_962218619 小时前
万象生鲜系统业财一体化底层打通技术自动生成经营账单
大数据·数据库·人工智能·python·算法
niucloud-admin9 小时前
JAVA V6 多商户商城 开发文档——插件目录结构
java·开发语言
2601_962885729 小时前
如何用 Python 扫描 A 股跳空缺口并统计缺口回补概率?
java·前端·python
吞下星星的少年·-·9 小时前
The 2026 ICPC Asia East Continent Online Contest (II)(构造)
数据结构·算法
滕州市燕猫虎计算机科技工作室个体工商户9 小时前
Java锁
java·开发语言
stolentime10 小时前
洛谷P10515 转圈题解
c++·算法·数学建模·贪心算法
shehuiyuelaiyuehao11 小时前
1计算机是如何工作的
java·计算机网络