更多算法题的题解见:算法刷题题解汇总(持续更新中)
Java数据结构与刷题常用方法
输入和输出
Scanner scanner = new Scanner(System.in); // 创建一个Scanner对象,用于从标准输入读取
- Scanner 类是 Java 中的一个用于文本扫描的实用程序,它可以从各种输入源(如文件、输入流、字符串等)中读取文本。以下是 Scanner 类的一些常用方法:
- Scanner(InputStream source): 构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
- Scanner(File source): 构造一个新的 Scanner,它生成的值是从指定的文件扫描的。
- Scanner(String source): 构造一个新的 Scanner,它生成的值是从指定的字符串扫描的。
- boolean hasNext(): 如果此扫描器的输入中有另一个token,则返回true。
- boolean hasNext(String pattern): 如果下一个token匹配指定的正则表达式,则返回 true。
- String next(): 查找并返回来自此扫描器的下一个完整token。
- String next(String pattern): 如果下一个token与指定的正则表达式匹配,则返回下一个token。
- int nextInt(): 将输入的下一个标记扫描为 int 值。
- long nextLong(): 将输入的下一个标记扫描为 long 值。
- float nextFloat(): 将输入的下一个标记扫描为 float 值。
- double nextDouble(): 将输入的下一个标记扫描为 double 值。
- String nextLine(): 此方法返回输入的下一行文本。
- Scanner useDelimiter(String pattern): 设置此扫描器使用的分隔模式。
- void close(): 关闭此扫描器。
- 以下是一个使用 Scanner 类的基本示例:
import java.util.Scanner;
public class ScannerExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建一个Scanner对象,用于从标准输入读取
System.out.print("Enter your name: ");
String name = scanner.next(); // 读取下一个字符串
System.out.print("Enter your age: ");
int age = scanner.nextInt(); // 读取下一个整数
System.out.print("Enter your height: ");
double height = scanner.nextDouble(); // 读取下一个double值
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Height: " + height);
scanner.close(); // 关闭scanner
}
}
在使用 Scanner 时,需要注意的是,如果输入的类型与期望的类型不匹配,将会抛出 InputMismatchException。此外,在使用完 Scanner 后应该调用 close() 方法来关闭它,以释放与其关联的系统资源。
常用方法
数组
获取长度 arr.length
3X4的表格:3行4列
二维数组int[[]][][] grid的行和列:
- 行:grid.length
- 列:grid[0].length
列表List
获取长度 list.size();
获取最大值 :
List list = Arrays.asList(1, 2, 3, 4, 5);
Integer max = Collections.max(list);
判断是否存在某些元素:contains()
List<String> wordDict
wordDictSet.contains("word")
clear()方法(力扣78)
List.clear()清空列表中所有元素
String
String s
**s.charAt(i):**返回字符串s
中索引为i
的位置上的字符
s.substring(j, i) :是String
类的一个方法,它返回字符串s
的一个子字符串。这个子字符串从索引j
开始,到索引i - 1
结束(不包括索引i
位置的字符)
j
:子字符串的起始索引(i 包含在子字符串中)。i
:子字符串的结束索引(j 不包含在子字符串中)。
char[] charArray = s.toCharArray():将字符串s
转换为一个字符数组,并将其赋值给charArray
变量。注意空字符也会存入
String s = "Hello World";
char[] charArray = s.toCharArray();
// 此时,charArray 的内容为:{'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'}
将char
字符转换成String
方法:
方法1:使用String
的valueOf
方法
char ch = 'a';
String str = String.valueOf(ch);
方法2:使用Character
类的toString
方法
char ch = 'a';
String str = Character.toString(ch);
方法3:使用String
构造函数
char ch = 'a';
String str = new String(new char[]{ch});
// 或者直接
String str = new String(new char[]{ch});
方法4:直接与空字符串相加
在Java中,如果你将char
类型与一个字符串相加,char
会被自动转换成String
。
char ch = 'a';
String str = "" + ch;
通常情况下,方法1和方法4是最常用和最简洁的。方法1非常明确地表示了转换的意图,而方法3则利用了Java的自动装箱和字符串连接特性,使代码更加简洁。根据具体的使用场景和编码风格,你可以选择最适合的方法。
StringBuffer与StringBuilder
``StringBuffer和
StringBuilder` 是 Java 中的两个类,它们都用于处理可变的字符序列,但它们之间有一些关键的区别。
- 线程安全性:
StringBuffer
是线程安全的。这意味着在多线程环境中,多个线程可以同时访问和修改同一个StringBuffer
实例,而不会出现线程安全问题。StringBuilder
不是线程安全的。在多线程环境中,如果多个线程同时访问和修改同一个StringBuilder
实例,可能会出现线程安全问题。
- 性能:
StringBuilder
通常比StringBuffer
更快,因为它不是线程安全的。在单线程环境中,使用StringBuilder
可以获得更好的性能。StringBuffer
在多线程环境中是安全的,但这也意味着它的性能可能会比StringBuilder
差一些。
- 使用场景:
- 如果你的应用程序需要处理多线程环境,并且需要保证字符串操作的线程安全,你应该使用
StringBuffer
。 - 如果你的应用程序是单线程的,或者你可以确保在单线程环境中使用,你应该使用
StringBuilder
,因为它通常更高效。
- 如果你的应用程序需要处理多线程环境,并且需要保证字符串操作的线程安全,你应该使用
- 内存分配:
StringBuffer
和StringBuilder
都实现了AbstractStringBuilder
类,它们内部都使用了一个字符数组(char[]
)来存储字符序列。- 当
StringBuffer
或StringBuilder
需要扩大其内部数组以容纳更多的字符时,它会创建一个新的字符数组,并将旧的字符数组复制到新数组中。这称为“数组复制”。
- 方法签名:
StringBuffer
类提供了一些方法,这些方法以Synchronized
开头,表示它们是线程安全的。StringBuilder
类的方法没有Synchronized
前缀,因此它们不是线程安全的。
在大多数情况下,如果你不需要线程安全,应该优先选择StringBuilder
,因为它通常更快。只有在确实需要线程安全的情况下,才应该使用StringBuffer
。
`
new StringBuffer()
是Java中创建一个StringBuffer
类实例的语句。StringBuffer
是一个可变的字符序列,它提供了一个线程安全的文本缓冲区,用于存储和操作字符串。
以下是 StringBuffer
的一些常用方法:
- 构造方法:
StringBuffer()
: 创建一个空的字符串缓冲区,初始容量为 16 个字符。StringBuffer(CharSequence seq)
: 创建一个包含指定字符序列的字符串缓冲区。StringBuffer(int capacity)
: 创建一个空的字符串缓冲区,初始容量由参数指定。
- 追加方法:
append(String str)
: 将指定的字符串追加到此字符序列。append(char c)
: 将指定的字符追加到此字符序列。append(int i)
: 将指定的整数值追加到此字符序列。
- 插入方法:
insert(int offset, String str)
: 将字符串插入此字符序列中。insert(int offset, char c)
: 将字符插入此字符序列中。insert(int offset, boolean b)
: 将布尔值转换为字符串后插入此字符序列中。
- 删除方法:
delete(int start, int end)
: 删除指定位置之间的字符。(左闭右开,含start,不含end)deleteCharAt(int index)
: 删除指定位置的字符。
- 替换方法:
replace(int start, int end, String str)
: 将指定位置的字符替换为指定的字符串。
- 反转方法:
reverse()
: 将此字符序列中的字符顺序反转。
- 其他方法:
length()
: 返回长度(字符数)。charAt(int index)
: 返回指定索引处的字符。substring(int start, int end)
: 返回一个新字符串,它是此序列的一个子序列。ensureCapacity(int minimumCapacity)
: 确保容量至少等于指定的最小值。setLength(int newLength)
: 设置字符序列的长度。
- 转换为字符串:
toString()
: 返回此序列中数据的字符串表示形式。
以下是关于 StringBuffer
的几个关键点:
- 可变性:与
String
类不同,StringBuffer
是可变的,这意味着你可以修改StringBuffer
的内容而不会创建一个新的对象。 - 线程安全:
StringBuffer
的所有公共方法都是同步的,这意味着它是线程安全的。在多线程环境中,多个线程可以安全地使用同一个StringBuffer
实例。 - 性能:由于
StringBuffer
是可变的,对它的操作(如添加、删除字符)通常比使用String
的操作更高效,因为StringBuffer
不需要在每次修改时都创建一个新的字符串对象。
下面是StringBuffer
的一个简单示例:
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.append(" World"); // 添加字符串到末尾
System.out.println(stringBuffer); // 输出 "Hello World"
在这个例子中,我们创建了一个 StringBuffer
实例,并使用 append
方法添加了一个字符串到它的末尾。
然而,从Java 5开始,StringBuilder
类被引入,它和 StringBuffer
类似,但是 StringBuilder
不是线程安全的。在单线程环境中,通常推荐使用 StringBuilder
,因为它比 StringBuffer
更快,因为它不需要考虑线程同步的开销。除非你需要线程安全,否则应该优先使用 StringBuilder
。
需要注意的是:
StringBuffer.toString()
方法会生成一个独立的 String
对象,这个对象包含了 StringBuffer
当前持有的字符序列。
当你调用 toString()
方法时,它会创建并返回一个新的 String
实例,这个实例的内容与 StringBuffer
中的内容相同。这个新的 String
对象是 StringBuffer
内容的一个副本,因此对 StringBuffer
的后续修改不会影响这个 String
对象。
这意味着在做题时,尤其是回溯类题目,当一个满足条件的回答成立时,可以直接.toString()然后加入List的ans,而不需要向List类型的combine加入List<List>类型的ans时,需要ans.add(new ArrayList<>(combine))
Integer
Integer
类中的最大值是Integer.MAX_VALUE,其值为2^31 - 1
,即2147483647
。
Integer
类中的最小值是Integer.MIN_VALUE,其值为-2^31
,即-2147483648
。
将单个字符转换为数字的方法
使用字符的ASCII值
每个字符在ASCII表中都有一个对应的整数值。对于数字字符 ‘0’ 到 ‘9’,它们的ASCII值分别是48到57。你可以通过减去 ‘0’ 的ASCII值来将字符转换为相应的数字:
char ch = '5';
int num = ch - '0'; // 结果是5
这个方法只适用于数字字符。
使用Character类的方法
Java的Character
类提供了getNumericValue(char ch)
方法,这个方法可以用于获取字符的数值,如果字符是数字字符的话:
char ch = '5';
int num = Character.getNumericValue(ch); // 结果是5
如果ch
不是数字字符,getNumericValue
方法会返回-1或者其它非数字的对应整数值。
示例代码
public class CharToNumber {
public static void main(String[] args) {
char ch = '5';
// 方法1: 使用ASCII值
int num1 = ch - '0';
System.out.println("Using ASCII value: " + num1);
// 方法2: 使用Character类的方法
int num2 = Character.getNumericValue(ch);
System.out.println("Using Character.getNumericValue: " + num2);
}
}
将String字符串转为int类型(常用Integer.valueOf(str)和 Integer.parseInt(s))
1. 使用基本数据类型的包装类
a. Integer.parseInt(String s)
将字符串转换为int类型:
String str = "123";
int num = Integer.parseInt(str);
b. Long.parseLong(String s)
将字符串转换为long类型:
String str = "123456789012345";
long num = Long.parseLong(str);
c. Float.parseFloat(String s)
将字符串转换为float类型:
String str = "123.45";
float num = Float.parseFloat(str);
d. Double.parseDouble(String s)
将字符串转换为double类型:
String str = "123.456789";
double num = Double.parseDouble(str);
2. 使用ValueOf方法
包装类的valueOf方法也可以将字符串转换为对应的数字类型:
String str = "123";
Integer num = Integer.valueOf(str);
3. 使用NumberFormat和DecimalFormat类
如果你需要处理更复杂的数字格式(例如,包含货币符号或千位分隔符),你可以使用NumberFormat
或DecimalFormat
类:
import java.text.NumberFormat;
import java.text.ParseException;
String str = "1,234.56";
NumberFormat format = NumberFormat.getInstance();
Number number = format.parse(str);
double num = number.doubleValue();
注意事项
- 当你使用上述方法时,如果字符串不能被解析为有效的数字,将会抛出
NumberFormatException
。 - 在解析前,确保字符串确实表示一个有效的数字,以避免运行时错误。
- 对于非数字字符,你需要进行额外的处理或验证。
下面是一个简单的例子,展示如何将字符串转换为数字,并处理可能的异常:
public class StringToNumber {
public static void main(String[] args) {
String str = "1234";
try {
int num = Integer.parseInt(str);
System.out.println("The number is: " + num);
} catch (NumberFormatException e) {
System.out.println("The string does not contain a parsable integer.");
}
}
}
使用这些方法,你可以根据需要将字符串转换为不同的数字类型。
Character(一般直接用静态方法)
Character
类是 Java 中的一个包装类,用于对基本数据类型 char
的对象进行操作。它包含了许多用于处理字符的静态方法和实例方法。以下是一些常用的 Character
类方法:
静态方法:
static boolean isLetter(char ch)
- 确定指定的字符是否为字母。
static boolean isDigit(char ch)
- 确定指定的字符是否为数字。
static boolean isWhitespace(char ch)
- 确定指定的字符是否为空白字符。
static boolean isUpperCase(char ch)
- 确定指定的字符是否为大写字母。
static boolean isLowerCase(char ch)
- 确定指定的字符是否为小写字母。
static char toUpperCase(char ch)
- 使用取自 UnicodeData 文件的大小写映射信息将字符转换为大写。
static char toLowerCase(char ch)
- 使用取自 UnicodeData 文件的大小写映射信息将字符转换为小写。
static int digit(char ch, int radix)
- 确定字符在指定基数中的数值。
static boolean isUpperCase(char ch)
- 确定指定的字符是否为大写字母。
static boolean isLowerCase(char ch)
- 确定指定的字符是否为小写字母。
static boolean isLetterOrDigit(char ch)
- 确定指定的字符是否为字母或数字。
static boolean isSpaceChar(char ch)
- 确定指定的字符是否为 ISO-LATIN-1 空格字符。
实例方法:
boolean isLetter()
- 确定此字符是否为字母。
boolean isDigit()
- 确定此字符是否为数字。
boolean isWhitespace()
- 确定此字符是否为空白字符。
boolean isUpperCase()
- 确定此字符是否为大写字母。
boolean isLowerCase()
- 确定此字符是否为小写字母。
char toUpperCase()
- 将此
Character
对象中的值转换为大写。
- 将此
char toLowerCase()
- 将此
Character
对象中的值转换为小写。
- 将此
int digit(int radix)
- 在给定的基数中,将此
Character
对象中的值转换为整数。
- 在给定的基数中,将此
boolean isUpperCase()
- 确定此字符是否为大写字母。
boolean isLowerCase()
- 确定此字符是否为小写字母。
boolean isLetterOrDigit()
- 确定此字符是否为字母或数字。
boolean isSpaceChar()
- 确定此字符是否为 ISO-LATIN-1 空格字符。
这些方法在处理文本和字符时非常有用,可以帮助开发者执行各种字符检查和转换操作。
- 确定此字符是否为 ISO-LATIN-1 空格字符。
HashMap
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
在Java中,HashMap
是一个基于哈希表的映射接口的实现,它存储键值对。以下是 HashMap
的一些基本方法:
构造方法
HashMap()
: 创建一个空的HashMap
。HashMap(int initialCapacity)
: 创建一个指定初始容量的空的HashMap
。HashMap(int initialCapacity, float loadFactor)
: 创建一个具有指定初始容量和负载因子的空的HashMap
。HashMap(Map<? extends K, ? extends V> m)
: 创建一个包含指定映射的HashMap
。
基本操作
void clear()
: 清除HashMap
中的所有键值对。boolean containsKey(Object key)
: 检查HashMap
是否包含指定的键。boolean containsValue(Object value)
: 检查HashMap
是否包含一个指定的值。boolean isEmpty()
: 检查HashMap
是否为空。V get(Object key)
: 返回指定键所映射的值,如果映射不包含该键的映射,则返回null
。V put(K key, V value)
: 将指定的键值对插入到HashMap
中。V remove(Object key)
: 从HashMap
中移除指定的键及其对应的值。
遍历操作
-
Set keySet(): 返回
HashMap
中所有key值的Set
视图。 -
Collection values(): 返回
HashMap
中所有value值的Collection
视图。 -
Set<Map.Entry<K, V>> entrySet(): 返回
HashMap
中所有键值对的Set
视图。**注意:**视图与
HashMap
同步。任何对HashMap
的修改(添加、删除键值对等)都会立即在视图中体现出来。同理,视图的任何更改(例如添加或删除操作)都会直接反映在原始的HashMap
上entrySet()用法:
import java.util.HashMap; import java.util.Map; import java.util.Set; public class Main { public static void main(String[] args) { // 创建一个HashMap实例 HashMap<Integer, String> map = new HashMap<>(); // 添加一些键值对 map.put(1, "苹果"); map.put(2, "香蕉"); map.put(3, "橙子"); // 获取所有键值对 Set<Map.Entry<Integer, String>> entrySet = map.entrySet(); // 遍历所有键值对 for (Map.Entry<Integer, String> entry : entrySet) { // 获取键和值 Integer key = entry.getKey(); String value = entry.getValue(); // 打印键值对 System.out.println("键: " + key + ", 值: " + value); } }
}
其他操作
int size()
: 返回HashMap
中的键值对数量。void putAll(Map<? extends K, ? extends V> m)
: 将指定映射中的所有映射复制到此映射中。V replace(K key, V value)
: 仅当指定键存在时,替换指定键的值。boolean replace(K key, V oldValue, V newValue)
: 仅当指定键存在并且当前值等于旧值时,替换指定键的值。
这些方法构成了HashMap
的基本操作集,可以用来实现大多数键值对映射的需求。需要注意的是,由于HashMap
允许一个键为null
,并且最多允许一个键为null
,还允许任意数量的值为null
,因此在使用这些方法时需要特别注意对null
值的处理。
Math
在Java中,Math
类提供了多种用于执行基本数学运算的方法。这些方法都是静态的,因此可以直接通过类名来调用,而无需创建Math
类的实例。以下是一些常用的Math
类方法:
基本数学运算
Math.abs(double a)
:返回a
的绝对值。Math.abs(int a)
:返回a
的绝对值。Math.max(double a, double b)
:返回a
和b
中的最大值。Math.max(int a, int b)
:返回a
和b
中的最大值。Math.min(double a, double b)
:返回a
和b
中的最小值。Math.min(int a, int b)
:返回a
和b
中的最小值。Math.sqrt(double a)
:返回a
的平方根。Math.cbrt(double a)
:返回a
的立方根。
获取长度(length,length(),size())
在Java中,length
和 length()
都是用来获取“长度”的,但是它们的使用场景是不同的:
-
length
属性:-
通常用于数组,用来获取数组的长度。
-
适用于所有类型的数组(如
int[]
,double[]
, `` 等)。 -
例如:
int[] numbers = new int[10]; int arrayLength = numbers.length; // 使用length属性获取数组长度
-
-
length()
方法:-
专用于
String
类,用来获取字符串中的字符数量。 -
例如:
String myString = "Hello, World!"; int stringLength = myString.length(); // 使用length()方法获取字符串长度
以下是使用
length
和length()
的具体情况:记住:String[ ]用.length,而String用length()
-
使用 length
的情况:
-
处理基本类型数组或对象数组时。
-
例如:
char[] chars = {'a', 'b', 'c'}; System.out.println(chars.length); // 输出:3
使用 length()
的情况:
-处理字符串时。
-
例如:
String text = "abcdef"; System.out.println(text.length()); // 输出:6
需要注意的是,Java中并没有直接提供获取集合(如
List
,Set
,Map
等)长度的length
属性或length()
方法。对于集合,你应该使用size()
方法来获取其中元素的数量。例如:
List<String> list = new ArrayList<>();
list.add("Item 1");
list.add("Item 2");
System.out.println(list.size()); // 输出:2
总之,记住 length
是数组的一个属性,而 length()
是 String
类的一个方法。
使用size()的情况
在Java中,除了数组和字符串之外,其他集合类型通常使用 size()
方法来获取它们包含的元素数量。以下是一些常见集合类型及其获取长度的方法:
-
ArrayList, LinkedList, Vector (实现 List 接口):
-
使用
size()
方法获取列表中的元素数量。 -
例如:
List<String> list = new ArrayList<>(); list.add("Item 1"); list.add("Item 2"); int size = list.size(); // 获取列表长度
-
-
HashSet, TreeSet, LinkedHashSet (实现 Set 接口):
-
使用
size()
方法获取集合中的元素数量。 -
例如:
Set<String> set = new HashSet<>(); set.add("Item 1"); set.add("Item 2"); int size = set.size(); // 获取集合长度
-
-
HashMap, TreeMap, LinkedHashMap (实现 Map 接口):
-
使用
size()
方法获取映射中的键值对数量。 -
例如:
Map<String, String> map = new HashMap<>(); map.put("Key 1", "Value 1"); map.put("Key 2", "Value 2"); int size = map.size(); // 获取映射长度
-
-
ArrayDeque, PriorityQueue (实现 Queue 接口):
-
使用
size()
方法获取队列中的元素数量。 -
例如:
Queue<String> queue = new ArrayDeque<>(); queue.add("Item 1"); queue.add("Item 2"); int size = queue.size(); // 获取队列长度
-
-
ArrayBlockingQueue, LinkedBlockingQueue (实现 BlockingQueue 接口):
-
使用
size()
方法获取阻塞队列中的元素数量。 -
例如:
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10); blockingQueue.add("Item 1"); blockingQueue.add("Item 2"); int size = blockingQueue.size(); // 获取阻塞队列长度
-
-
Stack (实现 Vector 类):
-
使用
size()
方法获取栈中的元素数量。 -
例如:
Stack<String> stack = new Stack<>(); stack.push("Item 1"); stack.push("Item 2"); int size = stack.size(); // 获取栈长度
这些方法返回的都是集合中的元素数量,它们适用于Java集合框架中的大多数集合类型。记住,对于数组,始终使用
length
属性;对于字符串,使用length()
方法;而对于其他集合类型,使用size()
方法。
-
三角函数
Math.sin(double a)
:返回a
的正弦值(以弧度为单位)。Math.cos(double a)
:返回a
的余弦值(以弧度为单位)。Math.tan(double a)
:返回a
的正切值(以弧度为单位)。
反三角函数
Math.asin(double a)
:返回a
的反正弦值(以弧度为单位)。Math.acos(double a)
:返回a
的反余弦值(以弧度为单位)。Math.atan(double a)
:返回a
的反正切值(以弧度为单位)。
指数和对数
Math.exp(double a)
:返回e
的a
次幂。Math.log(double a)
:返回a
的自然对数(以e
为底)。Math.log10(double a)
:返回a
的以10为底的对数。
幂运算
Math.pow(double a, double b)
:返回a
的b
次幂。
四舍五入
Math.round(double a)
:返回a
四舍五入后最接近的long
类型整数。Math.ceil(double a)
:返回大于或等于a
的最小整数值。Math.floor(double a)
:返回小于或等于a
的最大整数值。
随机数
Math.random()
:返回一个大于等于0.0且小于1.0的伪随机double
值。
其他
Math.PI
:返回圆周率π的值。Math.E
:返回自然对数的底数e的值。
这些方法涵盖了数学运算的许多方面,并且可以用于各种编程任务中。需要注意的是,三角函数和指数对数方法通常需要输入值以弧度为单位,如果使用的是角度,需要先将角度转换为弧度。
常用集合增加和移除元素的方法
在 Java 中,常用的集合类如 List
、Set
和 Queue
都有它们各自的方法来添加和移除元素。下面是一些常用的集合类及其增加和移除元素的方法:
List 接口
ArrayList
和LinkedList
是List
接口的两个常用实现。
添加元素:
add(E e)
:在列表的末尾添加元素。add(int index, E element)
:在指定索引处添加元素。
移除元素:
remove(int index)
:移除指定索引处的元素。remove(Object o)
:移除列表中首次出现的指定元素。clear()
:移除列表中的所有元素。
Set 接口
HashSet
、LinkedHashSet
和TreeSet
是Set
接口的常用实现。
添加元素:
add(E e)
:如果指定的元素不存在于集合中,则添加它。
移除元素:
remove(Object o)
:如果存在,则移除指定的元素。clear()
:移除集合中的所有元素。
Queue 接口
PriorityQueue
和LinkedList
(也可以用作队列)是Queue
接口的常用实现。
添加元素:
add(E e)
:添加元素,如果队列已满,抛出IllegalStateException
。offer(E e)
:添加元素,如果队列已满,返回false
。
移除元素:
remove()
:移除队列头部的元素,如果队列为空,抛出NoSuchElementException
。poll()
:移除队列头部的元素,如果队列为空,返回null
。clear()
:对于某些实现,如LinkedList
,可以用来清空队列。
下面是一个简单的示例,展示了如何使用这些方法:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
public class Main {
public static void main(String[] args) {
// List 示例
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add(1, "Banana");
arrayList.remove(0); // 移除第一个元素
arrayList.remove("Banana");
// Set 示例
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.remove("Apple");
hashSet.clear(); // 清空集合
// Queue 示例
Queue<String> queue = new LinkedList<>();
queue.add("Apple");
queue.offer("Banana");
queue.remove(); // 移除并返回队列头部元素
queue.poll(); // 移除并返回队列头部元素,如果队列为空,返回 null
queue.clear(); // 清空队列
}
}
这些是 Java 集合框架中添加和移除元素的基本方法。每个集合类可能还有其他特定于其实现的方法。在使用这些方法时,应该注意它们的返回类型和可能抛出的异常。
此外还有StringBuffer
和 StringBuilder
,以及 Queue
接口的其他一些方法:
StringBuffer 和 StringBuilder
这两个类用于构建可变的字符串序列。StringBuilder
是非线程安全的,因此在单线程环境中通常更受欢迎,因为它比 StringBuffer
更快。StringBuffer
是线程安全的,因此适用于多线程环境。
添加元素:
append(String str)
:将指定的字符串追加到序列的末尾。append(char c)
:将指定的字符追加到序列的末尾。insert(int offset, String str)
:将字符串插入到指定位置。
移除元素:
delete(int start, int end)
:移除序列中从 start 到 end(不包括 end)的字符。deleteCharAt(int index)
:移除指定位置的字符。setLength(int newLength)
:设置序列的长度,如果新长度小于当前长度,则会截断序列。
示例:
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
sb.insert(5, ", ");
sb.delete(0, 6); // 删除 "Hello, "
sb.deleteCharAt(sb.length() - 1); // 删除最后一个字符(空格)
常用集合获取长度的方法
在Java中,不同的集合类型有不同的方法来获取它们的长度或大小。以下是一些常用集合及其获取长度的方法:
1. 数组
数组有一个 length
属性来获取其长度。
int[] numbers = new int[10];
int length = numbers.length; // 获取数组的长度
2. List
List 接口有一个 size()
方法来获取列表的长度。
List<String> list = new ArrayList<>();
int size = list.size(); // 获取列表的长度
3. Set
Set 接口同样有一个 size()
方法来获取集合的长度。
Set<String> set = new HashSet<>();
int size = set.size(); // 获取集合的长度
4. Map
Map 接口有一个 size()
方法来获取键值对的数量。
Map<String, Integer> map = new HashMap<>();
int size = map.size(); // 获取Map的大小(键值对的数量)
5. Queue
Queue 接口也提供了 size()
方法来获取队列的长度。
Queue<String> queue = new LinkedList<>();
int size = queue.size(); // 获取队列的长度
6. Deque
Deque(双端队列)接口也有 size()
方法来获取队列的长度。
Deque<String> deque = new ArrayDeque<>();
int size = deque.size(); // 获取双端队列的长度
7. 其他集合类型
其他集合类型如 Stack、PriorityQueue 等,它们通常也有 size()
方法来获取集合的长度。
这些 size()
方法都是快速操作,通常具有 O(1) 的时间复杂度,因为集合内部维护了元素数量的计数。当你在使用这些集合时,应该使用这些方法来获取集合的长度或大小。
集合常用方法
Arrays静态方法
Arrays.fill()
在Java中,Arrays.fill()
,用于将指定的值分配给数组中所有或部分元素。这对于初始化数组元素特别有用,可以将整个数组或数组的一部分设置为相同的值。
以下是 Arrays.fill()
方法的一些常见用法:
填充整个数组
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = new int[5];
Arrays.fill(array, 10); // 将数组所有元素设置为10
System.out.println(Arrays.toString(array)); // 输出 [10, 10, 10, 10, 10]
}
}
填充数组的一部分
fill()
方法也可以用来填充数组的一个范围,需要指定起始索引(包括)和结束索引(不包括)。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = new int[5];
Arrays.fill(array, 0, 3, 10); // 将数组索引从0到2的元素设置为10
System.out.println(Arrays.toString(array)); // 输出 [10, 10, 10, 0, 0]
}
}
注意事项
- 使用
fill()
方法时,指定的值必须是数组元素类型的值。 - 如果指定的范围超出了数组的实际范围,会抛出
ArrayIndexOutOfBoundsException
。 fill()
方法会改变原数组,它不会创建一个新数组。
这个方法在处理数组时非常有用,尤其是在需要将数组初始化为默认值或清除数据时。
Collections
Collections.reverse():
用于反转指定列表中的元素顺序。
- 它接受一个
List
类型的参数。 - 它会直接在传入的列表上进行操作,而不是返回一个新的列表。
- 它不能用于
Set
、Map
或其他类型的集合,因为这些集合不保证元素的顺序,或者不支持顺序修改。
如果你尝试在一个不支持 List
接口的集合上使用 Collections.reverse
方法,编译器将会报错,因为这个方法要求传入的参数必须是 List
类型。
Collections.reverse() 可以反转 ArrayDeque和LinkedLList中元素的顺序
栈和队列(先看总结)
在Java中,栈(Stack)和队列(Queue)都是通过集合框架(Collections Framework)中的接口和类来实现的。这些接口和类位于java.util
包中。
组合:(注意ArrayDeque可以存地址但是不能存null,遇到需要存null值时统一用LinkedLList)
栈:push和poll、push和pop组合都可以实现栈(先进后出,后进先出)
队列:add和poll组合实现队列(先进先出,后进后出)
peek(),获得并返回最左侧元素但不删除元素,即查看最左侧元素,用在以上两个组合中可以分别查看栈顶和队列头部的元素
Collections.reverse() 可以反转 ArrayDeque和LinkedLList中元素的顺序
总结:
都可以用ArrayDeque和LinkedList来实现,其中有专门为队列写的Queue接口,没有为栈写的接口,栈可以用Queue的实现类进行实现。
- 注意ArrayDeque可以存地址但是不能存null,遇到需要存null值时统一用LinkedLList
- Queue是父类,子类有Deque,Deque的子类有ArrayDeque, LinkedList,且 LinkedList父类有List, Deque
- Deque、ArrayDeque、LinkedList有push()——元素加在最左侧、pop()——弹出最左侧的元素、offer()——元素加在最右侧、poll()——弹出最左侧的元素
- 但是Queue无push()、pop()
- List的remove与Queue的remove方法不同,Queue和Deque的remove()默认移除最左侧元素,remove(x)表示移除Queue中值为x的元素,即移除元素x;List的remove(x)必修传入参数,表示移除索引为x的元素。
对于列表list1: [1,2,3,4]
- push(x): 将元素x加在list1最左侧,即开头
- pop(): 弹出最左侧的元素。push和pop两个组合可以实现栈:先入后出,后入先出,因为先push会被后push的压到右边,新压入的元素在最左侧
- offer(x):将元素x加在list1最右侧,即末尾,
- poll():与pop的效果一样, 弹出最左侧的元素。两个组合实现队列:先入先出,后入后出,因为先offer入队会被后offer入队的压到左边,新压入的元素在最右侧,之前压入的元素在最左侧,而poll弹出最左侧元素,先入队列的被弹出了。
- **.add():将元素x加在list1最右侧(**对栈和队列都是加在最右侧)
- **.isEmpty()**判断是否为空,
- 对于Duque和queue,remove()是移除最左侧的元素,remove(x)是指移除数据结构中的x元素,而不是索引为x的元素。
- **但是有对于List,包括LinkedList,remove(x),remove中必须加参数,参数x代表索引,即Index为x的元素,与上一行不同。**ps:示例代码中:Queue queue = new LinkedList<>();queue的remove中的x为元素不是索引是因为父类是Queue,只能调用LinkedList实现的父类的Queue的方法,如果改为LinkedList queue = new LinkedList<>();此时remove()中必须要有参数,且参数为元素位置。
- 注意参数列表,index则为所以,o则为元素。
- peek(),获得最左侧元素但不删除元素,即查看最左侧元素,由于以上两个组合栈顶和队列头部的元素都在最左侧,所以peek()可以查看栈顶和队列头部元素,但需要时push和pop组合,offer和poll组合才行
- getLast()方法返回 ArrayDeque 和LinkedList的最右侧的元素。
- getFirst() 方法返回 ArrayDeque 的LinkedList最左侧的元素。
- removeLast() 方法移除并返回 最右侧的元素。
- removeFirst() 方法移除并返回 最左侧的元素。
- Collections.reverse()可反转集合中的元素顺序
需要注意的是:ArrayDeque和LinkedList拥有以上4个方法,但是以这种方式定义队列:Queue queue = new LinkedList<>(); Queue没有定义push和pop方法,所以不能使用。
以下是栈和队列的分别解释
栈(Stack)
Java并没有提供一个专门的Stack
接口,而是使用Deque
(双端队列)接口及其实现类来模拟栈的行为。Deque
接口提供了栈的所有功能,包括push
(入栈)和pop
(出栈)操作,以及peek
(查看栈顶元素)等。
以下是一些常用的实现Deque
接口的类:
ArrayDeque
:基于可变数组的实现,可以作为栈使用。LinkedList
:基于双向链表的实现,也可以作为栈使用。
例如,使用ArrayDeque
作为栈:
import java.util.ArrayDeque;
public class StackExample {
public static void main(String[] args) {
ArrayDeque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack.pop()); // 输出 3
System.out.println(stack.pop()); // 输出 2
System.out.println(stack.pop()); // 输出 1
}
}
队列(Queue)
Java提供了Queue
接口,用于表示队列数据结构。Queue
接口扩展了Collection
接口,并提供了特定的队列操作,如offer
(入队)、poll
(出队)、peek
(查看队列头部元素)等。
以下是一些常用的实现Queue
接口的类:
LinkedList
:基于双向链表的实现,可以作为队列使用。PriorityQueue
:基于优先级堆的无界优先队列。ArrayDeque
:基于可变数组的实现,也可以作为队列使用。
例如,使用LinkedList
作为队列:
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue.poll()); // 输出 1
System.out.println(queue.poll()); // 输出 2
System.out.println(queue.poll()); // 输出 3
}
}
在Java的集合框架中,Deque
接口同时实现了Queue
接口和Stack
的功能,因此它可以用来同时实现栈和队列的操作。
测试代码
import java.util.*;
public class t1 {
public static void main(String[] args) {
//List<Integer> L1 = new ArrayList<>();
ArrayDeque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.toString());
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue.remove(3));
System.out.println(queue.toString());
System.out.println(queue.poll()); // 输出 1
System.out.println(queue.poll()); // 输出 2
System.out.println(queue.poll()); // 输出 3
}
}
ArrayDeque(实现队列和栈)
实现栈
ArrayDeque
类也可以作为栈使用,它提供了 push
和 pop
方法来模拟栈的行为,其中 push
方法用于将元素压入栈顶,而 pop
方法用于从栈顶移除元素。
以下是这两个方法的详细描述:
push(E e)
:将元素 e 压入栈顶,即双端队列的前端。如果双端队列可以容纳更多的元素,则返回true
(实际上push
方法不会返回任何值,因为它是addFirst
方法的别名)。pop()
:从栈顶移除元素,即从双端队列的前端移除并返回第一个元素。如果双端队列为空,则抛出NoSuchElementException
异常。
下面是这两个方法的使用示例:
import java.util.ArrayDeque;
public class ArrayDequeAsStackExample {
public static void main(String[] args) {
ArrayDeque<Integer> stack = new ArrayDeque<>();
// 将元素压入栈
stack.push(10);
stack.push(20);
stack.push(30);
// 从栈顶移除元素
System.out.println(stack.pop()); // 输出 30
System.out.println(stack.pop()); // 输出 20
// 查看栈顶元素,但不移除它
System.out.println(stack.peek()); // 输出 10
// 再次从栈顶移除元素
System.out.println(stack.pop()); // 输出 10
}
}
如果栈为空,调用 pop
或 peek
将分别抛出 NoSuchElementException
或返回 null
。
实现队列
ArrayDeque
类还提供了 add
和 poll
方法,这些方法也可以用于队列操作。以下是这两个方法的详细描述:
add(E e)
:将指定的元素添加到双端队列的末尾。如果双端队列可以容纳更多的元素,则返回true
,否则抛出IllegalStateException
。在ArrayDeque
的实现中,除非达到其容量限制,否则总是返回true
。poll()
:检索并移除双端队列的头部元素,即第一个元素。如果双端队列为空,则返回null
。
下面是这两个方法的使用示例:
import java.util.ArrayDeque;
public class ArrayDequeAsQueueExample {
public static void main(String[] args) {
ArrayDeque<Integer> queue = new ArrayDeque<>();
// 将元素添加到队列的末尾
queue.add(10);
queue.add(20);
queue.add(30);
// 从队列的头部移除元素
System.out.println(queue.poll()); // 输出 10
System.out.println(queue.poll()); // 输出 20
// 查看队列的头部元素,但不移除它
System.out.println(queue.peek()); // 输出 30 或 null 如果队列为空
// 再次从队列的头部移除元素
System.out.println(queue.poll()); // 输出 30
// 尝试从空队列中移除元素
System.out.println(queue.poll()); // 输出 null
}
}
如果队列为空,调用 poll
或 peek
将分别返回 null
或返回 null
。需要注意的是,与 add
方法不同,offer
方法在添加元素时如果队列已满,则返回 false
而不是抛出异常。
其他方法
ArrayDeque
是 Java 中的一个双端队列实现,它可以用作栈和队列。以下是 ArrayDeque
的一些常用方法:
构造方法
ArrayDeque()
:创建一个空的双端队列。ArrayDeque(Collection<? extends E> c)
:创建一个包含指定集合的元素的双端队列。
添加元素
addFirst(E e)
:在双端队列的前端插入元素。addLast(E e)
:在双端队列的末尾添加元素,等效于add(E e)
。offerFirst(E e)
:在双端队列的前端插入元素,如果双端队列可以容纳,则返回true
。offerLast(E e)
:在双端队列的末尾添加元素,如果双端队列可以容纳,则返回true
。
删除元素
removeFirst()
:移除并返回双端队列的第一个元素。removeLast()
:移除并返回双端队列的最后一个元素。pollFirst()
:获取并移除双端队列的第一个元素,如果双端队列为空,则返回null
。pollLast()
:获取并移除双端队列的最后一个元素,如果双端队列为空,则返回null
。remove()
:移除第一个元素,等效于removeFirst()
。poll()
:获取并移除第一个元素,等效于pollFirst()
。
查看元素
getFirst()
:返回双端队列的第一个元素。getLast()
:返回双端队列的最后一个元素。peekFirst()
:**获取最左边的元素,**获取但不移除双端队列的第一个元素,如果双端队列为空,则返回null
。peekLast()
:**获取最右边的元素,**获取但不移除双端队列的最后一个元素,如果双端队列为空,则返回null
。peek()
:**获取最左边的元素,**但不移除第一个元素,等效于peekFirst()
。
其他方法
size()
:返回双端队列中的元素数量。isEmpty()
:如果双端队列为空,则返回true
。clear()
:清空双端队列。contains(Object o)
:如果双端队列包含指定的元素,则返回true
。iterator()
:返回此双端队列的迭代器。descendingIterator()
:返回此双端队列的逆序迭代器。
使用ArrayDeque
时需要注意,它不是线程安全的,如果多个线程同时访问一个ArrayDeque
实例,并且至少有一个线程修改了该结构,则必须保持外部同步。通常可以使用Collections.synchronizedDeque
方法来包装ArrayDeque
以实现同步。
Stack类
在Java中,Stack
类是 java.util
包中的一个类,它实现了标准的后进先出(LIFO)栈数据结构。以下是 Stack
类的一些基本操作:
创建一个 Stack
Stack<Integer> stack = new Stack<>();
基本操作
- push(E item): 将元素压入栈顶。
- pop(): 移除栈顶元素,并返回该元素。
- peek(): 返回栈顶元素,但不移除它。
- empty(): 检查栈是否为空。
- search(Object o): 返回对象在栈中的位置,从1开始计数,如果对象不在栈中,则返回-1。
示例
以下是如何使用 Stack
类的示例:
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 将元素压入栈
stack.push(10);
stack.push(20);
stack.push(30);
// 查看栈顶元素
System.out.println("栈顶元素: " + stack.peek()); // 输出: 30
// 移除栈顶元素
System.out.println("移除的元素: " + stack.pop()); // 输出: 30
// 再次查看栈顶元素
System.out.println("新的栈顶元素: " + stack.peek()); // 输出: 20
// 检查栈是否为空
System.out.println("栈是否为空: " + stack.isEmpty()); // 输出: false
// 查找元素在栈中的位置
System.out.println("元素20的位置: " + stack.search(20)); // 输出: 1
System.out.println("元素10的位置: " + stack.search(10)); // 输出: 2
System.out.println("元素40的位置: " + stack.search(40)); // 输出: -1 (元素不在栈中)
}
}
虽然 Stack
类在Java中仍然可用,但是基于早期Java集合框架的。现代Java代码更倾向于使用 Deque
接口及其实现,如 ArrayDeque
,因为 ArrayDeque
提供了更好的性能(通常更快的操作和更少的内存使用)。示例:
import java.util.ArrayDeque;
import java.util.Deque;
public class ArrayDequeAsStackExample {
public static void main(String[] args) {
Deque<Integer> stack = new ArrayDeque<>();
// 将元素压入栈
stack.push(10);
stack.push(20);
stack.push(30);
// 查看栈顶元素
System.out.println("栈顶元素: " + stack.peek()); // 输出: 30
// 移除栈顶元素
System.out.println("移除的元素: " + stack.pop()); // 输出: 30
// 再次查看栈顶元素
System.out.println("新的栈顶元素: " + stack.peek()); // 输出: 20
// 检查栈是否为空
System.out.println("栈是否为空: " + stack.isEmpty()); // 输出: false
}
}
编程细节
Java函数传入参数是否改变
在Java中,要想在方法内部改变外部变量的值,你需要理解Java的参数传递机制。Java总是按值传递参数,这意味着传递给方法的是值的一个副本。对于基本数据类型(如int、float等),这个副本就是数据值本身;对于对象(包括数组),这个副本是对象引用的一个拷贝。
如何改变外部变量的值
-
修改对象的状态:当你传递一个对象(包括数组)到一个方法时,虽然传递的是引用的副本,但这个副本和原始引用指向的是同一个对象。因此,如果方法内部改变了对象的状态(例如,修改数组的内容或更改对象的字段),那么这些变化对调用者是可见的。
void changeObjectState(MyObject obj) { obj.setState(42); // 改变对象的状态 }
-
使用返回值:如果需要改变基本数据类型的外部变量,可以通过方法返回值来实现。
int changeValue(int value) { return value + 1; // 返回修改后的值 }
如何只改变副本的值而不改变外部的值
-
修改参数副本:对于基本数据类型,如果你只是在方法内部修改参数的副本,那么原始变量的值不会改变。
void changeLocalCopy(int value) { value = 42; // 只改变本地副本的值 }
-
重新赋值引用:如果你在方法内部重新赋值对象引用,那么这个改变只在方法内部有效,不会影响原始引用。
void changeReference(MyObject obj) { obj = new MyObject(); // 重新赋值引用,只影响本地副本 }
总结来说,如果你想要在方法内部改变外部变量的值:
-
对于基本数据类型,使用返回值。
-
对于对象,直接修改对象的状态。
如果你不想要改变外部变量的值: -
对于基本数据类型,只需在方法内部操作参数的副本。
-
对于对象,避免重新赋值引用,只修改对象的状态。
例子:力扣189
方法一
class Solution {
public void rotate(int[] nums, int k) {
int len = nums.length;
int[] temp = new int[len];
for (int i = 0; i < len; i++) {
temp[(i+k)%len] = nums[i];
}
**System.arraycopy(temp,0,nums,0,len);//这里不能写nums = temp;**
}
}
原因:
在Java中,数组是对象,而对象是通过引用传递的。当你执行 nums = temp; 时,是在改变 nums 引用的指向,让它指向了 temp 所指向的数组。然而,这个改变只在 rotate 方法的局部作用域内有效,它并不会影响到调用 rotate 方法的代码中的 nums 数组。为Java的方法参数传递是按值传递的,当你传递一个数组(或其他对象)到一个方法时,你实际上传递的是这个数组引用的一个副本。在这个方法内部,nums 和 temp 都是这个引用副本的别名。 nums = temp; 时,你只是改变了这个副本指向的对象,并没有改变原始引用指向的对象。 而 System.arraycopy(temp, 0, nums, 0, len); 是不同的,它直接操作内存,temp 数组中的内容复制到 nums 数组中,这样 nums 数组的内容就改变了,这个改变是对调用者可见的。