内容

三元运算符

1
2
3
4
5
6
7
三元运算符(`l1 != null ? l1.val : 0`)相当于
int val1;
if (l1 != null) {
val1 = l1.val;
} else {
val1 = 0;
}

以下是关于链表的详细解答,涵盖你提到的所有问题:

链表


1. 如何新建一个链表

在 Java 中,链表是通过定义一个节点类(ListNode)来实现的。每个节点包含数据域和指针域。

定义链表节点

1
2
3
4
5
6
7
8
9
10
class ListNode {
int val; // 数据域
ListNode next; // 指针域,指向下一个节点

// 构造函数
ListNode(int val) {
this.val = val;
this.next = null;
}
}

创建链表

通过创建多个节点并链接它们来构建链表:

1
2
3
4
5
6
7
8
9
10
// 创建节点
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);

// 链接节点
node1.next = node2;
node2.next = node3;

// node1 -> node2 -> node3 -> null


2. 使用指针指向链表头节点

在链表中,通常使用一个指针(引用)来指向链表的头节点,以便操作链表。

1
ListNode head = node1;  // head 指向链表的头节点

3. 如何通过指针给链表数据域注入数据

可以通过指针访问链表节点的数据域并修改其值。

1
2
head.val = 10;  // 修改头节点的值
head.next.val = 20; // 修改第二个节点的值

4. 如何遍历注入数据

遍历链表时,可以通过指针逐个访问节点并修改数据域。

示例:遍历链表并修改每个节点的值

1
2
3
4
5
ListNode current = head;  // 定义指针 current,初始指向头节点
while (current != null) {
current.val = current.val * 2; // 修改当前节点的值
current = current.next; // 移动指针到下一个节点
}

5. 返回一个链表所有数据,怎么返回

可以通过遍历链表,将每个节点的数据域存储到一个列表(如 List)中,然后返回该列表。

示例:返回链表的所有数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.ArrayList;
import java.util.List;

public List<Integer> getLinkedListData(ListNode head) {
List<Integer> result = new ArrayList<>();
ListNode current = head;

// 遍历链表
while (current != null) {
result.add(current.val); // 将当前节点的值添加到列表
current = current.next; // 移动指针到下一个节点
}

return result; // 返回链表的所有数据
}

6. Java 中链表的特点

Java 中的链表具有以下特点:

(1)动态大小

  • 链表的大小可以动态增长或缩小,不需要预先分配固定大小的内存。

(2)插入和删除效率高

  • 在链表中插入或删除节点的时间复杂度为 O(1)(如果已知插入/删除位置)。
  • 相比于数组,链表在中间插入或删除数据时不需要移动其他元素。

(3)随机访问效率低

  • 链表不支持随机访问,访问第 n 个节点需要从头节点开始遍历,时间复杂度为 O(n)

(4)内存开销

  • 每个节点除了存储数据外,还需要额外的空间存储指针(引用),因此链表的内存开销比数组大。

(5)实现方式

  • Java 提供了内置的链表实现:java.util.LinkedList
  • LinkedList 是一个双向链表,支持在头部和尾部高效插入和删除数据。

(6)适用场景

  • 需要频繁插入和删除数据的场景。
  • 数据量不确定或动态变化的场景。
  • 不需要频繁随机访问数据的场景。

完整示例代码

以下是一个完整的 Java 示例,演示如何创建链表、遍历链表、修改数据并返回链表的所有数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class ListNode {
int val;
ListNode next;

ListNode(int val) {
this.val = val;
this.next = null;
}
}

public class LinkedListExample {
public static void main(String[] args) {
// 创建链表 1 -> 2 -> 3 -> null
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);

// 遍历链表并修改数据
ListNode current = head;
while (current != null) {
current.val = current.val * 2; // 修改每个节点的值
current = current.next;
}

// 返回链表的所有数据
List<Integer> result = getLinkedListData(head);
System.out.println(result); // 输出: [2, 4, 6]
}

// 返回链表的所有数据
public static List<Integer> getLinkedListData(ListNode head) {
List<Integer> result = new ArrayList<>();
ListNode current = head;

while (current != null) {
result.add(current.val);
current = current.next;
}

return result;
}
}

总结

  • 新建链表:通过定义节点类并链接节点。
  • 指针操作:使用指针(引用)指向链表头节点,并通过指针操作链表。
  • 遍历链表:通过循环逐个访问节点。
  • 返回链表数据:将链表数据存储到列表并返回。
  • 链表特点:动态大小、插入删除效率高、随机访问效率低。

数组

以下是正确的 for 循环写法:

1. 完整初始化

复制

1
2
3
for (int i = 0; i < nums.length; i++) {
// 循环体
}

2. 省略初始化(不推荐)

如果循环变量已经在外部定义,可以省略初始化部分,但必须保留分号:

复制

1
2
3
4
int i = 0;
for (; i < nums.length; i++) {
// 循环体
}

3. 省略所有部分(无限循环)

如果省略所有部分,for 循环将变成一个无限循环:

复制

1
2
3
for (;;) {
// 无限循环
}

4. 遍历数组

  • 使用 for 循环或 for-each 循环遍历数组。

  • 示例:

    复制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int[] nums = {1, 2, 3, 4, 5};

    // 使用 for 循环
    for (int i = 0; i < nums.length; i++) {
    System.out.println(nums[i]);
    }

    // 使用 for-each 循环
    for (int num : nums) {
    System.out.println(num);
    }

字符串

将两个二进制字符串相加并以二进制字符串的形式返回结果。

  1. 从字符串的末尾(最低位)开始逐位相加。

  2. 使用一个变量 carry 记录进位。

  3. 将每一位的相加结果拼接到结果字符串中。

  4. 最后反转结果字符串,得到正确的顺序。

        // 从末尾开始逐位相加
        while (i >= 0 || j >= 0 || carry != 0) {
            int sum = carry;  // 当前位的和
    
            // 如果 a 还有位,加上 a 的当前位
            if (i >= 0) {
                sum += a.charAt(i) - '0';  // 将字符转换为数字
                i--;
            }
    
            // 如果 b 还有位,加上 b 的当前位
            if (j >= 0) {
                sum += b.charAt(j) - '0';  // 将字符转换为数字
                j--;
            }
    
            // 计算当前位的结果和进位
            result.append(sum % 2);  // 当前位的结果
            carry = sum / 2;        // 进位值
        }
    
        // 反转结果字符串
        return result.reverse().toString();
    }
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        String a = "1010";
        String b = "1011";
        System.out.println(solution.addBinary(a, b));  // 输出: "10101"
    }
    

循环

  • if 语句:根据条件执行或不执行某段代码。

  • while 循环:当条件为真时,重复执行某段代码。

  • for 循环:根据条件重复执行某段代码,通常用于遍历或计数。

    (1)明确目标

    在设计条件判断之前,先明确程序的目标。

    例如:

    如果要遍历数组,条件判断可能是“是否到达数组末尾”。

    如果要查找某个元素,条件判断可能是“当前元素是否等于目标值”。

    (2)确定条件表达式

    条件表达式是一个布尔表达式,结果为 truefalse

    常见的条件表达式包括:

    比较运算:==, !=, >, <, >=, <=

    逻辑运算:&&(与),||(或),!(非)

    其他:instanceof(类型检查),contains(集合包含检查)

    (3)考虑边界条件

    确保条件判断覆盖所有可能的边界情况。

    例如:

    如果遍历数组,需要考虑数组为空的情况。

    如果查找元素,需要考虑元素不存在的情况。

    (4)优化条件判断

    尽量使条件判断简洁、清晰。

    避免重复的条件判断。

    使用适当的逻辑运算符组合条件。

全局变量与局部变量区别

特性 全局变量 局部变量
作用域 整个类中都可以访问 仅限于定义它的方法、代码块或循环内部
生命周期 从对象创建到对象销毁 从变量定义开始,到方法、代码块或循环结束
默认值 有默认值(如 int 默认为 0 没有默认值,必须显式初始化
访问权限 可以被类中的所有方法访问 只能在定义它的方法、代码块或循环中访问
内存分配 存储在堆内存中 存储在栈内存中