一、是什么

1.1 数组

数组是一种数据结构,用于存储相同类型的元素集合。数组中的元素在内存中是连续存放的,因此你可以通过索引(通常从0开始)直接访问每个元素。下面是对数组的详细介绍:

数组的基本特点

  1. 固定大小:数组的大小在创建时必须指定,之后不能动态改变。这意味着你必须提前知道需要存储多少数据。
  2. 元素类型相同:数组中的所有元素类型必须相同,例如整型数组只能存储整数,字符数组只能存储字符。
  3. 连续内存空间:数组中的元素在内存中是连续排列的,这让数组具有很高的访问效率。
  4. 通过索引访问:每个元素都有唯一的索引,索引从0开始(在大多数编程语言中),你可以通过索引快速访问任何元素。

数组的优点

  1. 快速访问:因为数组是连续存储的,能够通过索引直接访问任意元素,速度很快。
  2. 占用空间紧凑:数组在内存中连续存放,节省了空间。

数组的缺点

  1. 固定大小:数组的大小在创建时确定,之后不能改变。如果事先不知道需要存储的元素数量,可能会导致空间浪费或不足。
  2. 插入和删除不便:插入或删除元素时,需要移动数组中的其他元素,操作效率较低。

实际应用

数组通常用于存储一组相关的数据,如一组成绩、一段文本中的字符等。它们广泛用于各种算法中,尤其在排序和查找操作中。

1.2 字符串

字符串(String)是计算机编程中的一种数据类型,用来表示文本或字符序列。它通常由一组字符组成,字符可以是字母、数字、符号或其他可以表示的文本符号。

字符串的特点:

  1. 不可变性: 在许多编程语言(如 Python、Java 等)中,字符串是不可变的。即一旦创建了一个字符串,不能直接修改它的内容。如果需要对字符串进行修改,则会创建一个新的字符串对象。

  2. 长度可变: 字符串的长度可以是任意的,可以包含零个字符(空字符串)到非常多的字符。

  3. 索引和切片: 字符串中的每个字符都有一个位置索引。编程语言通常从 0 开始计数,因此可以通过索引访问字符串中的单个字符。还可以通过“切片”操作获取子字符串。

  4. 常见的操作

    • 拼接:将两个或多个字符串连接在一起。
    • 查找:查找某个字符或子字符串在字符串中的位置。
    • 替换:将字符串中的某个部分替换为其他内容。
    • 大小写转换:将字符串转换为全大写、全小写等。

字符串的用途:

字符串在编程中有非常广泛的应用,例如:

  • 存储和显示用户输入。
  • 构建和操作文件路径。
  • 处理自然语言文本,如在 NLP 任务中解析和生成文本。

二、为什么

2.1 数组

数组作为一种基础的数据结构,提供了高效的存储和访问机制。其主要优点和应用场景如下:

1. 快速访问数据

数组的最大特点之一是可以通过索引快速访问元素。这对于需要频繁读取数据的场景非常有用。例如,当你有一组学生的成绩,想知道某个特定学生的成绩时,通过数组可以快速定位。

例子:

students_scores = [85, 92, 78, 90, 88]
# 直接访问第三个学生的成绩
print(students_scores[2])  # 输出 78

2. 有序数据的存储

数组用于存储有序的数据。例如,当你想表示某个月份每天的温度记录时,可以用数组按照时间顺序来存储。

3. 节省内存

数组在内存中是连续存放的,这不仅保证了高效的访问,还能够减少内存的碎片化问题。与其他数据结构(如链表)相比,数组更紧凑,因为链表需要存储额外的指针。

4. 便于算法设计

数组作为很多复杂数据结构(如堆、栈、队列、矩阵等)的基础,在各种算法中具有重要地位。排序算法(如快速排序、归并排序)和查找算法(如二分查找)都依赖数组的结构来提高效率。

5. 适合静态数据

如果数据是固定大小且在使用过程中不需要频繁更改(如图片的像素点、音频的采样点等),数组可以作为非常合适的数据存储方式。

数组的历史

1. 起源

数组的概念可以追溯到早期的计算机科学发展时期,尤其是数值分析和数值计算的需求。早在20世纪40年代,随着电子计算机的诞生,科学家们发现需要一种结构来高效地存储和访问大规模的数字数据。最早的计算机程序设计语言之一是FORTRAN(1957年发布),其中就有对数组的支持。这使得科学家可以轻松地处理数值计算问题,如求解方程、矩阵运算等。

2. 语言中的引入

数组作为最基础的数据结构,几乎在所有编程语言中都有实现。在早期的汇编语言中,数组是通过固定的内存地址加上偏移量来实现的。随着编程语言的发展,高级语言如C语言、Pascal、BASIC等都引入了数组概念,使程序员能够更方便地管理数据。

  • C语言中的数组:C语言(1972年发布)对数组的实现非常经典。C中的数组就是一块连续的内存区域,可以通过指针操作直接访问和修改数组元素。C语言对数组操作的高效性奠定了其在系统编程中的重要地位。
  • Java和Python中的数组:Java和Python等现代编程语言也保留了数组的概念,虽然在这些语言中数组的使用更加高层次,但其背后依然依赖连续的内存管理。
3. 数组的扩展与变种

随着时间的推移,数组的概念也有所扩展。为了克服数组的一些缺点,如固定大小问题,人们设计了动态数组(如C++中的std::vector,Java中的ArrayList,Python中的列表 list)。这些变种提供了更灵活的操作方式,但其底层依然是基于传统的数组概念。

4. 多维数组和矩阵

在20世纪60年代和70年代,科学计算的兴起使得对多维数组的需求增加。多维数组允许以二维或更高维度的方式存储数据,这在图像处理、物理模拟、科学计算等领域非常重要。例如,一个二维数组可以用来表示矩阵,三维数组可以表示3D网格数据。

5. 现代应用中的数组
  • 科学计算与机器学习:在现代计算中,数组仍然是科学计算的核心。例如,Python中的NumPy库是基于数组的,专门用于处理大规模矩阵和向量运算。机器学习中的模型训练、图像处理、数据处理等都高度依赖数组结构。
  • 数据库与存储技术:数组概念也被引入到数据库系统中,用于高效存储和检索数据,尤其在列式存储数据库中,数据往往以数组的形式存储在磁盘上。

总结

数组作为一种基础的数据结构,因其高效的存储和访问方式,在早期计算机科学的发展中迅速占据了重要地位。尽管现代编程语言提供了更高级的数据结构(如链表、集合、哈希表等),但数组依然是许多应用中不可或缺的工具。其连续的内存布局、快速的访问时间使其在科学计算、图像处理和机器学习中依然发挥着重要作用。

2.2 字符串

字符串是计算机处理和表示文本信息的基础。在计算机和编程中,字符串起着至关重要的作用,原因包括:

  1. 文本处理: 许多应用程序都需要处理文本,例如:聊天应用、文档处理、网页设计、命令行输入等等。字符串为这些场景提供了表示和存储文本的结构。

  2. 数据交互: 在用户与程序的交互中,很多输入和输出都是基于文本的。例如,用户在网页上输入用户名和密码、系统生成的日志文件等,这些都需要使用字符串来存储和传递信息。

  3. 数据传输: 在不同系统或程序之间传递数据时,字符串通常是最常见的格式。比如,JSON 或 XML 文件,它们是以字符串的形式构建的结构化数据,用于不同应用程序之间进行数据交换。

  4. 信息存储: 字符串不仅可以存储单个字符,还可以存储词语、句子、段落甚至整个文档。许多文件格式(如纯文本文件)本质上都是字符串。

  5. 代码表示: 编程语言中,代码本身也可以以字符串的形式表示。例如,编写脚本、动态生成代码、解析代码时,字符串是非常常用的工具。

字符串的历史

字符串的历史和计算机科学的早期发展紧密相关。随着计算机和编程语言的发展,字符串的概念逐渐演变并变得更加复杂和多样化。

  1. 早期计算机与字符编码: 在早期的计算机时代,计算机只能处理数字。因此,工程师们发明了字符编码(如 ASCII)来让计算机能够理解和处理文本信息。每个字符(如字母、数字或符号)都被分配了一个唯一的数字编码,字符串则是这些编码的有序序列。

    ASCII(American Standard Code for Information Interchange) 是最早的标准字符编码系统之一,它定义了128个字符,包括英文字符、数字、标点符号和一些控制字符。ASCII 在20世纪60年代发展起来,最初用于电报系统,后来成为早期计算机系统处理文本的基础。

  2. 扩展字符集和 Unicode: 随着计算机的全球化,ASCII 的局限性逐渐显现。它只能处理128个字符,无法支持非英文字符,如中文、日文、阿拉伯文等。为了解决这个问题,字符集得到了扩展,如 ISO 8859 系列编码和 Windows-1252。

    但是,为了彻底解决全球语言字符的兼容性问题,Unicode 标准在1991年发布,目的是为每种语言的每个字符分配唯一的编码点。Unicode 支持全球几乎所有语言的字符,使字符串可以在任何地方统一表示。

  3. 字符串在编程语言中的发展: 随着编程语言的发展,字符串成为了一种重要的数据类型。早期的编程语言(如 C 语言)使用字符数组来表示字符串,字符串的管理(如长度、拼接等)需要手动处理。而随着高级编程语言(如 Python、Java)的出现,字符串变成了一种更为直观的、内置的数据类型,并提供了许多方便的操作函数,使得字符串处理变得更为简单高效。

  4. 字符串的现代应用: 随着互联网的发展,字符串在网络和信息交互中的应用越来越广泛。网页上的 HTML、JavaScript、JSON 数据传输格式、电子邮件、搜索引擎等都依赖于字符串来传递信息。此外,自然语言处理(NLP)技术的进步使得对字符串的处理更加复杂,包括分词、语义理解、文本生成等。

总结

字符串的发展从早期的简单字符编码到如今支持全球语言的 Unicode 标准,伴随着计算机科学的发展而不断演进。字符串不仅是文本表示的基本单位,也是现代计算机程序中最常用的数据类型之一,为我们提供了与机器交互、数据传递和处理的桥梁。

三、怎么办

3.1 数组

(一)数组的实现

// 声明一个数组并初始化
int[] arr = new int[5];  // 长度为5的数组,默认值为0
arr[0] = 10;             // 赋值给数组的第一个元素
arr[1] = 20;

// 也可以在声明时初始化
int[] arr2 = {1, 2, 3, 4, 5};  // 数组大小为5,元素依次为1, 2, 3, 4, 5

// 遍历数组
for (int i = 0; i < arr2.length; i++) {
    System.out.println(arr2[i]);
}

(二)数组的题目

1. 查找问题

查找是数组最基本的操作之一,题目可能会要求找到最大值、最小值、目标元素等。

例题: 给定一个整数数组,找到最大值。

public class FindMax {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30, 40, 50};
        int max = findMax(arr);
        System.out.println("最大值是: " + max);
    }

    public static int findMax(int[] arr) {
        int max = arr[0];  // 假设第一个元素为最大值
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        return max;
    }
}
2. 双指针问题

双指针是一种常见的数组处理技巧,适用于解决对撞指针和滑动窗口等问题。

例题: 给定一个排序数组,查找两个数,使它们的和等于给定的目标值。

解法: 使用双指针法,从数组的两端开始查找。

public class TwoSum {
    public static void main(String[] args) {
        int[] arr = {2, 7, 11, 15};
        int target = 9;
        int[] result = twoSum(arr, target);
        if (result != null) {
            System.out.println("找到的两个数的索引: " + result[0] + ", " + result[1]);
        } else {
            System.out.println("没有找到符合条件的两个数。");
        }
    }

    public static int[] twoSum(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        while (left < right) {
            int sum = arr[left] + arr[right];
            if (sum == target) {
                return new int[]{left, right};
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        return null;  // 没有找到
    }
}
3. 前缀和问题

前缀和用于解决子数组的和相关问题,通过计算累计和,可以快速得到任意子数组的和。

例题: 给定一个整数数组,找到连续子数组,使它们的和等于一个指定值。

解法: 使用前缀和来高效查找符合条件的子数组。

import java.util.HashMap;

public class SubarraySum {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 7, 5};
        int target = 12;
        boolean found = hasSubarrayWithSum(arr, target);
        System.out.println("是否找到和为目标值的子数组: " + found);
    }

    public static boolean hasSubarrayWithSum(int[] arr, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);  // 处理前缀和等于target的情况
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
            if (map.containsKey(sum - target)) {
                return true;
            }
            map.put(sum, i);
        }
        return false;
    }
}
4. 排序问题

经典的排序算法也是数组中常见的考察点,比如冒泡排序、快速排序、归并排序等。

例题: 使用冒泡排序对数组进行排序。

解法:

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        bubbleSort(arr);
        System.out.println("排序后的数组:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }

    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j] 和 arr[j + 1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

3. 总结

数组在算法题中非常常见,常见的考察方向有:

  1. 查找问题:如最大值、最小值、目标值的查找。
  2. 双指针问题:通过左右指针来高效解决一些特定问题。
  3. 前缀和问题:用于快速计算子数组的和。
  4. 排序问题:考察经典排序算法的实现。

3.2 字符串

(一)字符串的实现

字符串在 Java 中是通过 String 类实现的。String 类本质上是字符数组 char[] 的封装,并且字符串是不可变的,一旦创建后,字符串的内容就不能被修改。如果你对字符串进行操作(如拼接、替换等),会生成一个新的字符串对象,而不是修改原有的字符串。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    // 字符数组,用来存储字符串内容
    private final char value[];
    
    // 缓存的字符串哈希码
    private int hash;  // 默认值为 0
    
    // 构造函数
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

    // 获取字符串长度
    public int length() {
        return value.length;
    }

    // 获取字符串中某个字符
    public char charAt(int index) {
        if (index < 0 || index >= value.length) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

    // 字符串拼接操作
    public String concat(String str) {
        if (str.length() == 0) {
            return this;
        }
        char buf[] = new char[value.length + str.value.length];
        System.arraycopy(value, 0, buf, 0, value.length);
        System.arraycopy(str.value, 0, buf, value.length, str.value.length);
        return new String(buf);
    }

    // 字符串哈希码计算
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
}

(二)字符串的题目

1. 反转字符串

这是一个常见的基础题目,要求你将字符串中的字符顺序反转。

public class ReverseString {
    public static String reverse(String s) {
        // 将字符串转为字符数组
        char[] charArray = s.toCharArray();
        int left = 0, right = s.length() - 1;
        
        // 交换左右两个字符
        while (left < right) {
            char temp = charArray[left];
            charArray[left] = charArray[right];
            charArray[right] = temp;
            left++;
            right--;
        }
        // 返回新的字符串
        return new String(charArray);
    }

    public static void main(String[] args) {
        String s = "hello";
        System.out.println(reverse(s));  // 输出: "olleh"
    }
}
2. 判断回文字符串

判断一个字符串是否是回文串,即正读和反读相同。

public class PalindromeCheck {
    public static boolean isPalindrome(String s) {
        int left = 0, right = s.length() - 1;
        while (left < right) {
            // 跳过非字母数字字符
            while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
            while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
            
            if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

    public static void main(String[] args) {
        String s = "A man, a plan, a canal: Panama";
        System.out.println(isPalindrome(s));  // 输出: true
    }
}
3. 找到字符串中的第一个唯一字符

这是一个常见的算法题,要求你找到字符串中的第一个不重复的字符并返回它的索引。

import java.util.HashMap;

public class FirstUniqueChar {
    public static int firstUniqChar(String s) {
        HashMap<Character, Integer> count = new HashMap<>();
        
        // 统计每个字符的出现次数
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            count.put(c, count.getOrDefault(c, 0) + 1);
        }
        
        // 找到第一个只出现一次的字符
        for (int i = 0; i < s.length(); i++) {
            if (count.get(s.charAt(i)) == 1) {
                return i;
            }
        }
        
        return -1;  // 没有找到返回-1
    }

    public static void main(String[] args) {
        String s = "leetcode";
        System.out.println(firstUniqChar(s));  // 输出: 0 (因为 'l' 是第一个唯一字符)
    }
}
4. 最长公共前缀

给定一组字符串,找到它们的最长公共前缀。

public class LongestCommonPrefix {
    public static String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";
        String prefix = strs[0];
        
        for (int i = 1; i < strs.length; i++) {
            while (strs[i].indexOf(prefix) != 0) {
                prefix = prefix.substring(0, prefix.length() - 1);
                if (prefix.isEmpty()) return "";
            }
        }
        return prefix;
    }

    public static void main(String[] args) {
        String[] strs = {"flower", "flow", "flight"};
        System.out.println(longestCommonPrefix(strs));  // 输出: "fl"
    }
}

总结

在算法题中,字符串的考察重点通常是字符串的操作、模式匹配、解析和优化处理等。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐