|
| 1 | +package com.funian.algorithm.algorithm; |
| 2 | + |
| 3 | +import java.util.Map; |
| 4 | +import java.util.Scanner; |
| 5 | +import java.util.concurrent.ConcurrentHashMap; |
| 6 | + |
| 7 | +/** |
| 8 | + * 两数相加(LeetCode 2) |
| 9 | + * |
| 10 | + * 时间复杂度:O(max(m, n)) |
| 11 | + * - m 是第一个链表的长度,n 是第二个链表的长度 |
| 12 | + * - 需要遍历两个链表,直到两个链表都遍历完且无进位 |
| 13 | + * - 总时间复杂度为两个链表中较长者的长度 |
| 14 | + * |
| 15 | + * 空间复杂度:O(max(m, n)) |
| 16 | + * - 结果链表的长度最多为 max(m, n) + 1 |
| 17 | + * - 需要创建新节点存储结果 |
| 18 | + */ |
| 19 | +public class AddTwoList2 { |
| 20 | + |
| 21 | + /** |
| 22 | + * 链表节点定义 |
| 23 | + */ |
| 24 | + static class ListNode { |
| 25 | + int val; |
| 26 | + ListNode next; |
| 27 | + ListNode(int x) { val = x; } |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * 主函数:处理用户输入并输出两数相加的结果 |
| 32 | + * |
| 33 | + * 算法流程: |
| 34 | + * 1. 读取用户输入的两个链表 |
| 35 | + * 2. 调用addTwoNumbers方法计算两数之和 |
| 36 | + * 3. 输出结果链表 |
| 37 | + */ |
| 38 | + public static void main(String[] args) { |
| 39 | + Scanner scanner = new Scanner(System.in); |
| 40 | + |
| 41 | + // 输入第一个链表 |
| 42 | + System.out.println("输入第一个链表(用空格分隔每个节点的值):"); |
| 43 | + ListNode l1 = createList(scanner); |
| 44 | + |
| 45 | + // 输入第二个链表 |
| 46 | + System.out.println("输入第二个链表(用空格分隔每个节点的值):"); |
| 47 | + ListNode l2 = createList(scanner); |
| 48 | + |
| 49 | + // 调用addTwoNumbers方法计算两数之和 |
| 50 | + ListNode result = addTwoNumbers(l1, l2); |
| 51 | + System.out.println("结果链表:"); |
| 52 | + printList(result); |
| 53 | + } |
| 54 | + |
| 55 | + /** |
| 56 | + * 创建链表的辅助方法 |
| 57 | + * |
| 58 | + * 算法思路: |
| 59 | + * 使用哑节点简化链表创建过程 |
| 60 | + * 依次将输入的数值转换为链表节点 |
| 61 | + * |
| 62 | + * 时间复杂度分析: |
| 63 | + * - 遍历输入数组:O(k),其中k为输入数字个数 |
| 64 | + * |
| 65 | + * 空间复杂度分析: |
| 66 | + * - 创建链表节点:O(k) |
| 67 | + * |
| 68 | + * @param scanner Scanner对象用于读取输入 |
| 69 | + * @return 创建的链表头节点 |
| 70 | + */ |
| 71 | + private static ListNode createList(Scanner scanner) { |
| 72 | + // 读取一行输入 |
| 73 | + String line = scanner.nextLine(); |
| 74 | + // 按空格分割字符串得到字符串数组 |
| 75 | + String[] strArray = line.split(" "); |
| 76 | + // 创建哑节点 |
| 77 | + ListNode dummy = new ListNode(0); |
| 78 | + // 当前节点指针,初始指向哑节点 |
| 79 | + ListNode current = dummy; |
| 80 | + |
| 81 | + // 遍历字符串数组 |
| 82 | + for (String s : strArray) { |
| 83 | + // 创建新节点并连接到链表中 |
| 84 | + current.next = new ListNode(Integer.parseInt(s)); |
| 85 | + // 移动当前节点指针 |
| 86 | + current = current.next; |
| 87 | + } |
| 88 | + // 返回链表的头节点(哑节点的下一个节点) |
| 89 | + return dummy.next; |
| 90 | + |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * 打印链表的辅助方法 |
| 95 | + * |
| 96 | + * 算法思路: |
| 97 | + * 从头节点开始依次遍历并打印每个节点的值 |
| 98 | + * |
| 99 | + * 时间复杂度分析: |
| 100 | + * - 遍历链表:O(m),其中m为链表长度 |
| 101 | + * |
| 102 | + * 空间复杂度分析: |
| 103 | + * - 只使用常数额外空间:O(1) |
| 104 | + * |
| 105 | + * @param head 链表的头节点 |
| 106 | + */ |
| 107 | + private static void printList(ListNode head) { |
| 108 | + // 遍历链表直到末尾 |
| 109 | + while (head != null) { |
| 110 | + // 打印当前节点的值 |
| 111 | + System.out.print(head.val + " "); |
| 112 | + // 移动到下一个节点 |
| 113 | + head = head.next; |
| 114 | + } |
| 115 | + // 换行 |
| 116 | + System.out.println(); |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * 两数相加的核心方法 |
| 121 | + * |
| 122 | + * 算法思路: |
| 123 | + * 模拟手工加法过程,从低位到高位依次相加 |
| 124 | + * 处理进位情况,直到两个链表都遍历完且无进位 |
| 125 | + * |
| 126 | + * 执行过程分析(以 l1=[2,4,3], l2=[5,6,4] 为例,表示 342 + 465 = 807): |
| 127 | + * |
| 128 | + * 初始状态: |
| 129 | + * l1: 2 -> 4 -> 3 -> null (表示数字 342) |
| 130 | + * l2: 5 -> 6 -> 4 -> null (表示数字 465) |
| 131 | + * dummy -> null |
| 132 | + * current -> dummy |
| 133 | + * carry = 0 |
| 134 | + * |
| 135 | + * 第1位相加(2 + 5): |
| 136 | + * sum = 0 + 2 + 5 = 7 |
| 137 | + * carry = 7 / 10 = 0 |
| 138 | + * 创建节点:new ListNode(7 % 10) = new ListNode(7) |
| 139 | + * dummy -> 7 -> null |
| 140 | + * current -> 7 |
| 141 | + * l1: 4 -> 3 -> null |
| 142 | + * l2: 6 -> 4 -> null |
| 143 | + * |
| 144 | + * 第2位相加(4 + 6): |
| 145 | + * sum = 0 + 4 + 6 = 10 |
| 146 | + * carry = 10 / 10 = 1 |
| 147 | + * 创建节点:new ListNode(10 % 10) = new ListNode(0) |
| 148 | + * dummy -> 7 -> 0 -> null |
| 149 | + * current -> 0 |
| 150 | + * l1: 3 -> null |
| 151 | + * l2: 4 -> null |
| 152 | + * |
| 153 | + * 第3位相加(3 + 4): |
| 154 | + * sum = 1 + 3 + 4 = 8 |
| 155 | + * carry = 8 / 10 = 0 |
| 156 | + * 创建节点:new ListNode(8 % 10) = new ListNode(8) |
| 157 | + * dummy -> 7 -> 0 -> 8 -> null |
| 158 | + * current -> 8 |
| 159 | + * l1: null |
| 160 | + * l2: null |
| 161 | + * |
| 162 | + * 循环结束(l1和l2都为null,且carry为0) |
| 163 | + * |
| 164 | + * 返回 dummy.next,即 7 -> 0 -> 8 -> null(表示数字 807) |
| 165 | + * |
| 166 | + * 时间复杂度分析: |
| 167 | + * - 遍历两个链表:O(max(m, n)),其中m为第一个链表长度,n为第二个链表长度 |
| 168 | + * - 每次循环执行常数时间操作 |
| 169 | + * |
| 170 | + * 空间复杂度分析: |
| 171 | + * - 结果链表节点数:O(max(m, n)) |
| 172 | + * - 常数额外变量:O(1) |
| 173 | + * |
| 174 | + * @param l1 第一个数的链表表示(逆序存储) |
| 175 | + * @param l2 第二个数的链表表示(逆序存储) |
| 176 | + * @return 两数之和的链表表示(逆序存储) |
| 177 | + */ |
| 178 | + public static ListNode addTwoNumbers(ListNode l1, ListNode l2) { |
| 179 | + // 哑节点,用于简化链表操作 |
| 180 | + ListNode dummy = new ListNode(0); |
| 181 | + // 当前节点指针,初始指向哑节点 |
| 182 | + ListNode current = dummy; |
| 183 | + // 进位值,初始为0 |
| 184 | + int carry = 0; |
| 185 | + |
| 186 | + // 遍历两个链表,直到两个链表都遍历完且无进位 |
| 187 | + while (l1 != null || l2 != null || carry != 0) { |
| 188 | + // 初始化和为进位 |
| 189 | + int sum = carry; |
| 190 | + |
| 191 | + // 如果 l1 不为空,则加上 l1 的值 |
| 192 | + if (l1 != null) { |
| 193 | + sum += l1.val; |
| 194 | + l1 = l1.next; |
| 195 | + } |
| 196 | + |
| 197 | + // 如果 l2 不为空,则加上 l2 的值 |
| 198 | + if (l2 != null) { |
| 199 | + sum += l2.val; |
| 200 | + l2 = l2.next; |
| 201 | + } |
| 202 | + |
| 203 | + // 计算新的进位 |
| 204 | + carry = sum / 10; |
| 205 | + // 创建新节点,保存当前位的值 |
| 206 | + current.next = new ListNode(sum % 10); |
| 207 | + // 移动到下一个节点 |
| 208 | + current = current.next; |
| 209 | + } |
| 210 | + |
| 211 | + // 返回结果链表(哑节点的下一个节点) |
| 212 | + return dummy.next; |
| 213 | + } |
| 214 | + |
| 215 | +} |
0 commit comments