数据结构
java自带数据结构框架
string常用方法
scanner常用方法
arraylist常用方法
算法工具
反转
(需要完整的列表内容,while(in.hasnext()),就会不能确认,反转不了)
Collctions.reverse(list)//反转list中内容
去重
在 Java 中,有多种便捷的方法可以去除集合中的重复元素。最常见和快捷的方法是使用 Set
数据结构,因为 Set
不允许有重复的元素。Java 提供了几个 Set
实现类,如 HashSet
、LinkedHashSet
和 TreeSet
,这些类都可以方便地用于去重操作。
下面是几种常见的去重方式:
1. 使用 HashSet
去重
HashSet
是最常用的去重工具,因为它基于哈希表实现,添加和查找操作的时间复杂度为 O(1),非常高效。但需要注意的是,HashSet
不保证元素的顺序。
示例代码:
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
public class RemoveDuplicates {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(2);
list.add(4);
list.add(3);
// 使用 HashSet 去重
Set<Integer> set = new HashSet<>(list);
// 如果需要再次转换回 List
List<Integer> uniqueList = new ArrayList<>(set);
System.out.println("去重后的列表: " + uniqueList);
}
}
输出:
去重后的列表: [1, 2, 3, 4]
2. 使用 LinkedHashSet
去重并保持顺序
如果你希望去重后保留原来的元素插入顺序,可以使用 LinkedHashSet
。它内部使用了一个双向链表来维护插入顺序,因此既能去重又能保持顺序。
示例代码:
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
public class RemoveDuplicates {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("apple");
list.add("orange");
// 使用 LinkedHashSet 去重并保持顺序
Set<String> set = new LinkedHashSet<>(list);
// 如果需要再次转换回 List
List<String> uniqueList = new ArrayList<>(set);
System.out.println("去重并保持顺序后的列表: " + uniqueList);
}
}
输出:
去重并保持顺序后的列表: [apple, banana, orange]
3. 使用 TreeSet
去重并自动排序
如果你需要去重并按照自然顺序(或自定义排序规则)对元素排序,可以使用 TreeSet
。TreeSet
基于红黑树实现,添加元素时会自动排序。
示例代码:
import java.util.TreeSet;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
public class RemoveDuplicates {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(2);
list.add(8);
list.add(3);
list.add(5);
// 使用 TreeSet 去重并自动排序
Set<Integer> set = new TreeSet<>(list);
// 如果需要再次转换回 List
List<Integer> uniqueList = new ArrayList<>(set);
System.out.println("去重并排序后的列表: " + uniqueList);
}
}
输出:
去重并排序后的列表: [2, 3, 5, 8]
4. 使用 Java 8 的 Stream API 去重
在 Java 8 及更高版本中,可以使用 Stream
API 的 distinct()
方法对集合进行去重。distinct()
方法会返回一个无重复元素的 Stream
,可以配合 collect()
方法将结果转换回 List
或 Set
。
示例代码:
import java.util.List;
import java.util.stream.Collectors;
import java.util.ArrayList;
public class RemoveDuplicates {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("apple");
list.add("orange");
// 使用 Stream API 去重
List<String> uniqueList = list.stream().distinct().collect(Collectors.toList());
System.out.println("使用 Stream API 去重后的列表: " + uniqueList);
}
}
输出:
使用 Stream API 去重后的列表: [apple, banana, orange]
5. 手动遍历去重
如果你不想使用 Set
或 Stream
,可以手动通过遍历实现去重。通常使用一个辅助的 List
或 Set
来存储已经遇到的元素,跳过重复项。
示例代码:
import java.util.ArrayList;
import java.util.List;
public class RemoveDuplicates {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(2);
list.add(4);
list.add(3);
List<Integer> uniqueList = new ArrayList<>();
for (Integer i : list) {
if (!uniqueList.contains(i)) {
uniqueList.add(i);
}
}
System.out.println("手动去重后的列表: " + uniqueList);
}
}
输出:
手动去重后的列表: [1, 2, 3, 4]
总结
HashSet
是最常用且效率较高的去重方法,但不保留元素的插入顺序。LinkedHashSet
可以在去重的同时保留元素的插入顺序。TreeSet
去重并对元素自动排序(自然顺序或自定义顺序)。Stream API
的distinct()
是 Java 8 及以上版本中较为简洁的去重方法。- 手动遍历去重 适用于需要自定义逻辑的场景,但效率较低。
根据具体需求,你可以选择不同的去重方式。如果追求效率和简洁,Set
是首选。
1、计算字符串最后一个单词的长度,单词以空格隔开
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
String[] s = str.split(" "); //正则表达式实用性更强( str.split("\\s+"))
int length = s[s.length - 1].length();
System.out.println(length);
}
2、计算某字母出现次数
代码解释:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String input1 = s.nextLine(); // 读取第一行输入,一个字符串
String input2 = s.nextLine(); // 读取第二行输入,一个字符或子字符串
// 将两个输入都转换为大写,并用 replaceAll 方法将 input1 中所有与 input2 相匹配的字符去掉
String split3 = input1.toUpperCase().replaceAll(input2.toUpperCase(), "");
// 计算原始字符串和去掉匹配字符后的字符串长度差,并输出这个差值
System.out.println(input1.length() - split3.length());
}
}
主要概念:
replaceAll(String regex, String replacement)
:- 该方法用于替换字符串中所有匹配指定正则表达式 (
regex
) 的子字符串,并用给定的替换字符串 (replacement
) 替换它们。 - 在这个例子中,代码将
input1
中所有与input2
相同的字符(不区分大小写)替换为空字符串(即删除),并将替换后的新字符串存储在split3
中。
- 该方法用于替换字符串中所有匹配指定正则表达式 (
实现逻辑:
- 用户输入一个字符串
input1
和一个字符或子字符串input2
。 input1.toUpperCase()
和input2.toUpperCase()
将输入转换为大写形式,确保在比较时不区分大小写。- 使用
replaceAll
将所有input1
中与input2
相同的字符删除。 - 原始字符串的长度减去删除后的字符串的长度,就得到了该字符在字符串中出现的次数。
示例:
如果输入:
input1
: “ABCabc”input2
: “A”
运行结果将输出 2
,因为字母 “A” 和 “a” 在原字符串中共出现了两次。
通过这种方式,replaceAll
可以有效地替换(删除)字符串中的指定字符或子字符串。
3、随机数数组排序去重
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int n = scanner.nextInt();
int[] intArr = new int[n];
for (int i = 0; i < n; i++) {
intArr[i] = scanner.nextInt();
}
Arrays.sort(intArr);//使用sort进行排序,默认升序,使用的comparator进行比较
for (int i = 0; i < intArr.length; i++) {
// 第一个数字或者不等于前一个数字都可以输出
if (i == 0 || intArr[i] != intArr[i - 1]) {
System.out.println(intArr[i]);
}
}
}
}
}
4、字符串分隔
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String str = sc.nextLine();
StringBuilder sb = new StringBuilder();//牢记字符串缓冲区的建立语法
sb.append(str);//字符串缓冲区的加入
int size = str.length();
int addZero = 8 - size % 8; //addzero的可能值包括8
while ((addZero > 0) && (addZero < 8)) { //注意边界调节,避免addzero=8
sb.append("0");//使用‘’或“”都可
addZero--;
}
String str1 = sb.toString();
while (str1.length() > 0) {
System.out.println(str1.substring(0, 8));
str1 = str1.substring(8);
}
}
}
}
解析
在你提供的代码中,StringBuilder
是关键部分,它用于高效地操作可变字符串。我们先从代码的逻辑开始解释,然后专门分析 StringBuilder
在其中的作用。
代码逻辑简要:
- 输入读取:使用
Scanner
获取用户输入。 - 字符串操作:
- 创建
StringBuilder
对象用于拼接输入的字符串和填充的 ‘0’。 - 如果输入的字符串长度不是 8 的倍数,就在字符串末尾添加
0
,直到长度变为 8 的倍数。
- 创建
- 分段输出:将处理后的字符串每 8 个字符一组进行输出。
代码解析:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String str = sc.nextLine(); // 读取输入的字符串
- 这部分从控制台读取用户输入的字符串。
StringBuilder sb = new StringBuilder(); // 创建一个空的 StringBuilder
sb.append(str); // 将输入的字符串追加到 StringBuilder 中
int size = str.length(); // 计算输入字符串的长度
int addZero = 8 - size % 8; // 计算需要补齐的 '0' 的个数,确保字符串长度是 8 的倍数
StringBuilder sb = new StringBuilder();
:StringBuilder
是一个用于创建可变字符串的类,它允许我们对字符串执行修改操作(如追加、插入等)而不会产生新的字符串对象。sb.append(str);
:将用户输入的字符串追加到StringBuilder
中,这里可以直接操作字符串,而不需要创建新的字符串对象。int addZero = 8 - size % 8;
:计算出需要补齐的0
的数量。
while ((addZero > 0) && (addZero < 8)) { // 如果需要补齐 '0'
sb.append("0"); // 逐个追加 '0' 到 StringBuilder 中
addZero--; // 递减补齐的 '0' 数量
}
String str1 = sb.toString(); // 将 StringBuilder 转换为最终的字符串
sb.append("0");
:将0
追加到字符串的末尾,这里StringBuilder
通过追加操作直接在原字符串基础上进行修改,不像普通的字符串String
那样每次操作都会生成新的对象,因此效率更高。String str1 = sb.toString();
:将StringBuilder
对象转换为一个String
,准备进行下一步操作(分割字符串并输出)。
while (str1.length() > 0) {
System.out.println(str1.substring(0, 8)); // 每次取出前8个字符(序号0到序号7)
str1 = str1.substring(8); // 去掉已经输出的8个字符
}
}
}
}
str1.substring(0, 8)
:从字符串的前 8 个字符中取出一部分并打印。str1 = str1.substring(8);
:将剩下的字符串截取出来,继续下一轮的输出。
StringBuilder
:
-
高效拼接字符串:
- 在 Java 中,字符串
String
是不可变的(immutable
),每次对String
执行操作(如拼接、修改等)都会生成新的字符串对象,旧的对象会被丢弃。这种操作在需要大量修改字符串的场景下效率低下。 StringBuilder
是可变的字符串类,允许我们对原字符串进行修改,而不需要创建新的字符串对象。它通过内部可调整的字符数组实现这一点,极大提高了字符串拼接的效率。
- 在 Java 中,字符串
-
避免创建过多中间对象:
- 在这段代码中,每次对字符串追加 ‘0’ 的操作,如果使用普通的
String
,每次操作都要创建一个新的字符串对象,效率非常低。而StringBuilder
能直接在原字符串基础上修改,不需要额外创建对象,减少了内存占用和垃圾回收的压力。
- 在这段代码中,每次对字符串追加 ‘0’ 的操作,如果使用普通的
-
用于构建和处理可变字符串:
- 通过
StringBuilder
的append()
方法,可以灵活地在现有的字符串后追加任意内容,操作简单且高效。 - 最终通过
toString()
方法将StringBuilder
的内容转化为String
类型,便于输出和进一步处理。
- 通过
StringBuilder
在这段代码中主要用于高效地拼接字符串。由于输入的字符串可能不足 8 位,所以通过StringBuilder
将输入字符串和需要补齐的 ‘0’ 一起拼接,然后通过toString()
转换为最终的字符串,最后按照需求每 8 位输出一次。这种方法比直接使用String
拼接高效得多。StringBuilder
是 Java 中用于创建和操作可变字符串的类。与String
不同,StringBuilder
的内容可以被修改,因此在需要频繁拼接、修改字符串时,StringBuilder
会比String
更高效。下面是StringBuilder
的一些常见用法:
1. 创建 StringBuilder
对象
可以通过无参或带参构造方法来创建 StringBuilder
对象。
-
无参构造:创建一个空的
StringBuilder
,默认容量为 16 个字符。StringBuilder sb = new StringBuilder();
-
带参构造:传入一个初始字符串或指定容量大小。
StringBuilder sb = new StringBuilder("Hello"); StringBuilder sb2 = new StringBuilder(50); // 容量为50
2. append()
方法
append()
是 StringBuilder
最常用的方法,它用于在 StringBuilder
的末尾追加内容。可以追加各种类型的内容(String
、int
、char
、boolean
等)。
StringBuilder sb = new StringBuilder();
sb.append("Hello ");
sb.append("World");
sb.append(123); // 可以添加数字
System.out.println(sb.toString()); // 输出 "Hello World123"
3. insert()
方法
insert()
用于在 StringBuilder
的指定位置插入内容。可以插入字符串、数字等。
StringBuilder sb = new StringBuilder("Hello World");
sb.insert(6, "Beautiful "); // 在索引6的位置插入
System.out.println(sb.toString()); // 输出 "Hello Beautiful World"
4. replace()
方法
replace()
用于替换指定范围内的字符序列。起始索引是包含的,结束索引是排除的。
StringBuilder sb = new StringBuilder("Hello World");
sb.replace(6, 11, "Java"); // 替换 "World" 为 "Java"
System.out.println(sb.toString()); // 输出 "Hello Java"
5. delete()
和 deleteCharAt()
方法
delete(int start, int end)
:删除指定范围的字符。deleteCharAt(int index)
:删除指定索引的字符。
StringBuilder sb = new StringBuilder("Hello World");
sb.delete(5, 11); // 删除 " World"
System.out.println(sb.toString()); // 输出 "Hello"
sb.deleteCharAt(4); // 删除索引4的字符 'o'
System.out.println(sb.toString()); // 输出 "Hell"
6. reverse()
方法
reverse()
用于将 StringBuilder
中的字符序列反转。
StringBuilder sb = new StringBuilder("Hello World");
sb.reverse(); // 反转
System.out.println(sb.toString()); // 输出 "dlroW olleH"
7. setCharAt()
方法
setCharAt(int index, char ch)
用于将指定位置的字符替换为新的字符。
StringBuilder sb = new StringBuilder("Hello World");
sb.setCharAt(6, 'J'); // 将索引6处的字符替换为 'J'
System.out.println(sb.toString()); // 输出 "Hello Jorld"
8. capacity()
和 ensureCapacity()
方法
capacity()
:返回StringBuilder
当前的容量。ensureCapacity(int minimumCapacity)
:确保StringBuilder
的容量至少为minimumCapacity
。
StringBuilder sb = new StringBuilder();
System.out.println(sb.capacity()); // 输出 16 (默认初始容量)
sb.ensureCapacity(50); // 设置最小容量为 50
9. length()
方法
length()
返回 StringBuilder
中字符的数量。
StringBuilder sb = new StringBuilder("Hello");
System.out.println(sb.length()); // 输出 5
10. substring()
方法
substring(int start)
或 substring(int start, int end)
:从 StringBuilder
中获取一个子字符串。
StringBuilder sb = new StringBuilder("Hello World");
String subStr = sb.substring(6); // 从索引6开始获取子串
System.out.println(subStr); // 输出 "World"
11. toString()
方法
toString()
用于将 StringBuilder
转换为 String
。这是最常用的操作之一,特别是在拼接完字符串后。
StringBuilder sb = new StringBuilder("Hello World");
String result = sb.toString(); // 转换为 String
System.out.println(result); // 输出 "Hello World"
示例:使用 StringBuilder
进行字符串拼接和操作
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("Java");
sb.append(" Programming");
// 插入字符串
sb.insert(5, "is Fun ");
// 替换字符串
sb.replace(0, 4, "Learning");
// 删除字符
sb.delete(13, 16);
// 反转字符串
sb.reverse();
// 打印结果
System.out.println(sb.toString());
}
}
总结:
StringBuilder
提供了一系列高效的字符串操作方法,适合频繁的字符串拼接、修改和处理操作。它的主要优点在于可变性,相比于不可变的 String
,使用 StringBuilder
进行大量的字符串操作时可以避免产生过多的中间对象,从而提高性能。
5、进制转换
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws Exception{
Scanner sc = new Scanner(System.in);
while(sc.hasNextLine()){
String s = sc.nextLine();
// 截取去掉 "0x" 前缀的十六进制数字部分
System.out.println(Integer.parseInt(s.substring(2,s.length()),16));
}
}
}
核心实现:
-
substring(2, s.length())
:- 由于输入的十六进制数通常以 “0x” 开头(如 “0xAA”),所以通过
substring()
方法跳过前两个字符,只保留真正的十六进制数部分。 - 例如,“0xAA” 会被截取为 “AA”。
- 由于输入的十六进制数通常以 “0x” 开头(如 “0xAA”),所以通过
-
Integer.parseInt(s, 16)
:Integer.parseInt()
方法可以将字符串转换为整数,并且允许指定进制。parseInt(String s, int radix)
:这里的radix
为16
,表示输入的字符串是一个十六进制数。- 例如,
Integer.parseInt("AA", 16)
会将十六进制数 “AA” 解析为十进制的170
。
Integer
类的相关知识:
Integer
是 Java 中的一个包装类,它封装了基本类型 int
,并提供了操作 int
值的工具方法。以下是 Integer
类的一些常见功能和方法:
1. Integer.parseInt(String s)
- 将字符串解析为
int
类型,字符串必须是由数字组成。 - 如果字符串包含非数字字符,则抛出
NumberFormatException
。 - 例如:
int num = Integer.parseInt("123"); // 返回 123
2. Integer.parseInt(String s, int radix)
- 将指定进制的字符串解析为整数,
radix
可以是 2(表示二进制)、8(表示八进制)、10(表示十进制)、16(表示十六进制)等。 - 例如:
int num = Integer.parseInt("FF", 16); // 返回 255 (十六进制的 FF)
3. Integer.toString(int i)
- 将整数
i
转换为十进制的字符串表示形式。 - 例如:
String str = Integer.toString(123); // 返回 "123"
4. Integer.toString(int i, int radix)
- 将整数
i
按指定进制转换为字符串。 - 例如,将十进制数转换为十六进制:
String hex = Integer.toString(255, 16); // 返回 "ff"
5. Integer.valueOf(String s)
- 与
parseInt()
类似,但返回的是Integer
对象而不是int
基本类型。 - 例如:
Integer num = Integer.valueOf("123"); // 返回 Integer 对象,值为 123
6. Integer.valueOf(String s, int radix)
- 与
parseInt(String s, int radix)
类似,但返回的是Integer
对象。 - 例如:
Integer num = Integer.valueOf("FF", 16); // 返回 Integer 对象,值为 255
7. 常量
Integer.MAX_VALUE
:int
类型的最大值(2^31 - 1
,即 2147483647)。Integer.MIN_VALUE
:int
类型的最小值(-2^31
,即 -2147483648)。
8. 自动装箱与拆箱
Integer
是int
的包装类,在需要Integer
对象的地方,Java 会自动将int
类型装箱为Integer
对象。- 在需要
int
基本类型的地方,Java 会自动将Integer
对象拆箱为int
。 - 例如:
Integer obj = 100; // 自动装箱 int num = obj; // 自动拆箱
9. Integer.compare(int x, int y)
- 用于比较两个
int
值,返回一个整数结果,表示比较结果:- 如果
x < y
,返回负数; - 如果
x == y
,返回 0; - 如果
x > y
,返回正数。
- 如果
- 例如:
int result = Integer.compare(5, 10); // 返回 -1
代码中的 parseInt
是如何工作的:
- 当调用
Integer.parseInt(s.substring(2, s.length()), 16)
时,Java 内部会依次读取字符串的每个字符,并将它们按照十六进制规则转换为十进制数。 - 例如,字符串
"AA"
对应的十六进制数为A
表示 10,A
表示 10,因此 “AA” 对应的十进制数为10 * 16 + 10 = 170
。
总结:
Integer.parseInt()
是将字符串解析为整数的常用方法,可以解析不同进制的数字字符串。Integer
类提供了很多静态方法来处理int
类型的数据,如数字转换、比较、解析字符串等。- 自动装箱与拆箱是
Integer
类与基本数据类型int
之间的一种方便操作,可以在需要int
或Integer
时自动完成转换。
6、质数因子
public class Main{
public static void main(String[] args) {
// 处理输入
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
// 获取需要求解的值
int target = sc.nextInt();
int y = 2;// 因子从2开始算
while(target != 1){ // 短除法,除到目标值为1为止
if(target % y == 0) // 能能够整除2
{
System.out.print(y+" ");
target /= y;
}else{// 更新y的值
if(y > target / y) y = target;//如果剩余值为质数
else y++; //y值增加1
}
}
}
}
}
7、取近似值
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
double number = in.nextDouble();
//强制类型转换采取的截断的方式,通过加零点五,就可以实现
System.out.println((int)(number + 0.5));
}
}
8、合并表记录
由于要求顺序,所以不能简单用hashset,按升序的话,treemap即可自动升序
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextInt()) { // 注意 while 处理多个 case
int tableSize = in.nextInt();
Map<Integer, Integer> table = new TreeMap<>();
for (int i = 0; i < tableSize; i++) {
int key = in.nextInt();
int value = in.nextInt();
if (table.containsKey(key)) {
table.put(key, table.get(key) + value);
} else {
table.put(key, value);
}
}
for (Integer key : table.keySet()) {
System.out.println( key + " " + table.get(key));
}
}
}
}
遍历
在代码中,使用了 Map
来存储键值对(key-value
),并且对输入的数据进行了处理。随后,通过 for
循环对 Map
进行遍历,并将每个键对应的值输出。下面详细解释代码中遍历 Map
的实现方式。
代码片段分析:
for (Integer key : table.keySet()) {
System.out.println(key + " " + table.get(key));
}
这个循环是用来遍历 HashMap
中所有的键(key
),然后使用 get(key)
方法来获取对应的值(value
)。
Map
的遍历方式:
-
table.keySet()
:keySet()
方法返回Map
中所有键的一个Set
视图。Set
中的每个键都是唯一的(因为Map
中的键不能重复),所以我们通过遍历这个键集合来访问Map
中的每个键值对。
-
for (Integer key : table.keySet())
:- 这是一个增强的
for
循环,它遍历table.keySet()
,即Map
中所有的键。 - 对于每个键,
key
变量会依次被赋予keySet()
中的值。
- 这是一个增强的
-
table.get(key)
:get()
方法用于根据键获取对应的值。- 在循环中,对于每个键,使用
table.get(key)
获取它对应的值。
完整遍历的过程:
- 获取键的集合:通过
table.keySet()
方法,获取Map
中所有键的集合。 - 循环遍历键集合:对于每一个键,通过
for
循环进行遍历。 - 获取对应值:在循环体内,通过
table.get(key)
方法,获取该键对应的值。 - 输出结果:将键和值组合输出。
例子:
假设输入如下(两个键值对 1 10
和 2 20
):
2 // 输入数据的大小
1 10
2 20
table
的内容会是:
{
1 = 10,
2 = 20
}
通过遍历 keySet()
,循环会依次获取键 1
和键 2
,然后通过 table.get(1)
得到 10
,通过 table.get(2)
得到 20
,最后输出:
1 10
2 20
Map
遍历的其他方式:
除了通过 keySet()
来遍历键值对,Map
还有其他几种常见的遍历方式:
1. 遍历 entrySet()
(同时获取键和值):
for (Map.Entry<Integer, Integer> entry : table.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
entrySet()
返回一个包含Map.Entry
对象的集合,每个Entry
对象都包含键和值。entry.getKey()
:获取键。entry.getValue()
:获取对应的值。
2. 使用 forEach
方法(Java 8+):
table.forEach((key, value) -> System.out.println(key + " " + value));
forEach()
方法可以更简洁地遍历Map
中的所有键值对。
总结:
遍历 Map
的方式是通过 keySet()
方法获取所有的键,然后使用增强的 for
循环来遍历键,再通过 get(key)
方法获取对应的值并输出。还有其他几种遍历方式(如 entrySet()
和 forEach()
),可以根据需要选择合适的方式。
9、反转去重
法一:list反转,set去重
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
List<Integer> list = new ArrayList<>();
// 读取输入的整行,并将每个数字添加到列表
String input = in.nextLine(); // 读取整行输入
String[] numbers = input.split(""); // 按字符进行分割
for (String num : numbers) {
list.add(Integer.parseInt(num)); // 转换并添加到列表
}
// 反转 List
Collections.reverse(list);
// 将反转后的 List 转换为 LinkedHashSet 进行去重
Set<Integer> set = new LinkedHashSet<>(list);
// 打印去重后的元素(按反转后的顺序)
for (Integer num : set) {
System.out.print(num);
}
}
}
法二:字符判定
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
char[] chars= (num+"").toCharArray();
String str ="";
for(int i= chars.length-1; i>= 0;i--){
if(!str.contains(chars[i]+"")){
str +=chars[i];
}
}
System.out.println(Integer.valueOf(str));
}
}
10、字符个数统计
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str=in.nextLine();
Set<Character> lin=new LinkedHashSet();//通过set进行去重
for (char c:str.toCharArray()){//通过char遍历字符串加入元素到集合中
lin.add(c);
}
System.out.print(lin.size());
}
}
11、数字颠倒(反转字符)
import java.util.Scanner;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str=in.nextLine();
StringBuffer sb=new StringBuffer(str);
sb.reverse();
System.out.println(sb);
}
}
StringBuilder
和 StringBuffer
都是 Java 中用于创建和操作可变字符串的类,它们的主要区别在于 线程安全性 和 性能,此外它们的方法也非常相似,主要提供对字符串进行修改的功能。
1. StringBuilder
和 StringBuffer
的区别
线程安全性:
StringBuffer
:是线程安全的。它的所有方法都被synchronized
修饰,意味着多个线程可以安全地访问同一个StringBuffer
实例,而不会引发并发问题。StringBuilder
:不是线程安全的。它没有提供线程同步机制,因此在单线程环境下或者不需要线程安全的情况下使用效率更高。
性能:
StringBuilder
:因为没有线程安全机制,性能比StringBuffer
高。在单线程操作可变字符串时,StringBuilder
是首选。StringBuffer
:由于提供了线程同步机制,性能会稍逊于StringBuilder
。它适合在多线程环境下使用。
2. 相同点
- 可变性:与
String
类不同,StringBuilder
和StringBuffer
都允许对字符串进行修改,不会像String
那样每次修改都创建新的对象。 - 相同的 API:
StringBuilder
和StringBuffer
拥有几乎相同的方法,它们提供了非常类似的功能,包括追加、插入、删除、反转等操作。
3. 主要方法
1. append()
:追加字符串
- 将指定的字符串或字符序列追加到现有的内容之后。
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb); // 输出: Hello World
2. insert()
:插入字符串
- 在指定位置插入字符串。
StringBuilder sb = new StringBuilder("Hello");
sb.insert(5, " World");
System.out.println(sb); // 输出: Hello World
3. delete()
:删除字符串
- 删除指定索引范围的字符。
StringBuilder sb = new StringBuilder("Hello World");
sb.delete(5, 11); // 删除索引 5 到 10 的字符
System.out.println(sb); // 输出: Hello
4. deleteCharAt()
:删除指定索引的字符
- 删除特定索引处的字符。
StringBuilder sb = new StringBuilder("Hello");
sb.deleteCharAt(4);
System.out.println(sb); // 输出: Hell
5. replace()
:替换字符串
- 用指定的字符串替换指定索引范围内的字符。
StringBuilder sb = new StringBuilder("Hello World");
sb.replace(6, 11, "Java");
System.out.println(sb); // 输出: Hello Java
6. reverse()
:反转字符串
- 反转字符串中字符的顺序。
StringBuilder sb = new StringBuilder("Hello");
sb.reverse();
System.out.println(sb); // 输出: olleH
7. setCharAt()
:设置某个位置的字符
- 修改指定索引处的字符。
StringBuilder sb = new StringBuilder("Hello");
sb.setCharAt(0, 'J');
System.out.println(sb); // 输出: Jello
8. toString()
:转换为 String
- 将
StringBuilder
或StringBuffer
对象转换为String
。
StringBuilder sb = new StringBuilder("Hello");
String str = sb.toString();
System.out.println(str); // 输出: Hello
9. length()
和 capacity()
length()
:获取当前字符串的长度。capacity()
:获取当前StringBuilder
或StringBuffer
对象的容量。
StringBuilder sb = new StringBuilder("Hello");
System.out.println(sb.length()); // 输出: 5
System.out.println(sb.capacity()); // 输出: 默认容量 16 + 字符串长度
10. ensureCapacity()
:确保容量
- 确保
StringBuilder
或StringBuffer
的容量至少为给定的值。
StringBuilder sb = new StringBuilder();
sb.ensureCapacity(50); // 确保容量至少为 50
4. 性能比较
在单线程环境中,StringBuilder
是性能较优的选择。因为它没有额外的同步机制,速度更快,尤其是在频繁对字符串进行修改时。
在多线程环境中,如果多个线程可能同时访问同一个字符串对象,则应该使用 StringBuffer
来确保线程安全。
5. 选择指南
StringBuilder
:适合在单线程环境下使用,或者在不需要线程同步的场景中。一般情况下,推荐使用StringBuilder
,因为它的性能更好。StringBuffer
:适合在多线程环境下使用,尤其是当多个线程需要共享和修改同一个字符串时,应该选择StringBuffer
,因为它提供了线程安全性。
总结
特性 | StringBuilder | StringBuffer |
---|---|---|
线程安全 | 否 | 是 |
性能 | 更高(因为没有同步机制) | 较低(同步开销较大) |
适用场景 | 单线程环境 | 多线程环境 |
主要方法 | append() , insert() , delete() , replace() , reverse() , toString() , setCharAt() | append() , insert() , delete() , replace() , reverse() , toString() , setCharAt() |
在实际开发中,如果你确定你的字符串操作是在单线程环境中运行,使用 StringBuilder
是更好的选择。如果你的程序中存在多线程并发修改字符串的情况,则应该选择 StringBuffer
。
13、句子逆序
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str=in.nextLine();
String[] s=str.split(" ");
for(int i=s.length;i>0;i--){
if(i!=1){
System.out.print(s[i-1]+" ");
}
else{
System.out.print(s[i-1]);
}
}
}
}
14、字符串排序(字典序)
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取整数 n,表示接下来的字符串数量
int n = sc.nextInt();
sc.nextLine(); // 消耗掉换行符,只有nextline会包括换行符
// 创建一个列表来存储输入的字符串
List<String> words = new ArrayList<>();
// 读取 n 个字符串
for (int i = 0; i < n; i++) {
String word = sc.nextLine();
words.add(word);
}
// 对字符串按照字典顺序进行排序(默认升序)
Collections.sort(words);
// 输出排序后的字符串
for (String word : words) {
System.out.println(word);
}
}
}
15、整数转换进制,统计数量(Integer.toBinaryString(number)转二进制)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 读取输入的整数
int number = in.nextInt();
// 将整数转换为二进制字符串
String binaryString = Integer.toBinaryString(number);
// 统计二进制字符串中 '1' 的个数
int count = 0;
for (int i = 0; i < binaryString.length(); i++) {
if (binaryString.charAt(i) == '1') {
count++;
}
}
// 输出结果
System.out.println(count);
}
}
注意:tostring是转换为十进制,要转二进制字符串使用包装类的toBinaryString
16、购物单(动态规划)
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()) {
int money = sc.nextInt();
int m = sc.nextInt();
sc.nextLine();
money /= 10;
int[][] prices = new int[m+1][3];
int[][] weights = new int[m+1][3];
for (int i = 1; i <= m; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
a /= 10;//price
b = b * a;//weight
if (c == 0) {
// 主件
prices[i][0] = a;
weights[i][0] = b;
} else if (prices[c][1] == 0) {
// 附件1
prices[c][1] = a;
weights[c][1] = b;
} else {
// 附件2
prices[c][2] = a;
weights[c][2] = b;
}
sc.nextLine();
}
int[][] dp = new int[m+1][money+1];
for (int i = 1; i <= m; i++) {
for(int j = 1; j <= money; j++) {
int a = prices[i][0];
int b = weights[i][0];
int c = prices[i][1];
int d = weights[i][1];
int e = prices[i][2];
int f = weights[i][2];
dp[i][j] = j - a >= 0 ? Math.max(dp[i-1][j], dp[i-1][j-a] + b) : dp[i-1][j];
dp[i][j] = j-a-c >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-c] + b + d):dp[i][j];
dp[i][j] = j-a-e >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-e] + b + f):dp[i][j];
dp[i][j] = j-a-c-e >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-c-e] + b +d + f):dp[i][j];
}
}
System.out.println(dp[m][money] * 10);
}
}
}
这个问题是一个带依赖的01背包问题,涉及到主件和附件的购买。我们需要根据预算和物品的依赖关系,选择能够带来最大“满意度”的组合。你的代码解决了这个问题,具体做法是通过动态规划来处理,并考虑了主件和附件的不同组合方式。下面我们详细解析这个代码的实现逻辑。
问题解析
-
输入:
N
:预算,代表可以花的钱数。m
:物品的数量。- 每个物品有价格
v
,重要度p
,以及一个标志q
,表示是主件还是附件。
-
主件和附件关系:
- 主件可以单独购买。
- 附件必须与主件一起购买。
- 每个主件最多有两个附件。
-
目标:
- 最大化购买的“满意度”,即
价格 * 重要度
的和。
- 最大化购买的“满意度”,即
代码解析
1. 输入处理
int money = sc.nextInt();
int m = sc.nextInt();
money /= 10; // 将预算转换为 1/10 单位
- 先读取预算
money
和物品数量m
,因为题目中每个物品的价格是 10 的倍数,所以将预算和价格都除以 10,方便后续计算。
2. 构建物品的价格和重要度数组
int[][] prices = new int[m+1][3]; // 价格数组
int[][] weights = new int[m+1][3]; // 满意度数组
prices[i][0]
表示第i
件物品的主件价格。prices[i][1]
和prices[i][2]
分别表示第i
件物品的两个附件价格。weights[i][0]
表示主件的满意度(价格 * 重要度
)。weights[i][1]
和weights[i][2]
分别表示两个附件的满意度。
3. 读取物品信息并处理主件和附件
if (c == 0) {
// 主件
prices[i][0] = a;
weights[i][0] = b;
} else if (prices[c][1] == 0) {
// 附件1
prices[c][1] = a;
weights[c][1] = b;
} else {
// 附件2
prices[c][2] = a;
weights[c][2] = b;
}
- 如果
c == 0
,说明当前物品是主件,将价格和满意度存储在prices[i][0]
和weights[i][0]
。 - 如果
c > 0
,说明当前物品是附件,prices[c][1]
是第一个附件的位置,prices[c][2]
是第二个附件的位置。
4. 动态规划数组初始化
int[][] dp = new int[m+1][money+1];
dp[i][j]
表示在考虑前i
件物品,且花费不超过j
元时的最大满意度。
5. 动态规划状态转移
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= money; j++) {
int a = prices[i][0]; // 主件价格
int b = weights[i][0]; // 主件满意度
int c = prices[i][1]; // 附件1价格
int d = weights[i][1]; // 附件1满意度
int e = prices[i][2]; // 附件2价格
int f = weights[i][2]; // 附件2满意度
dp[i][j] = j - a >= 0 ? Math.max(dp[i-1][j], dp[i-1][j-a] + b) : dp[i-1][j];
dp[i][j] = j-a-c >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-c] + b + d):dp[i][j];
dp[i][j] = j-a-e >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-e] + b + f):dp[i][j];
dp[i][j] = j-a-c-e >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-c-e] + b +d + f):dp[i][j];
}
}
-
dp[i][j]
:表示考虑前i
件物品,花费不超过j
元时的最大满意度。 -
主要的动态规划方程考虑了以下几种情况:
- 只购买主件:
dp[i][j] = dp[i-1][j-a] + b
。 - 购买主件和第一个附件:
dp[i][j] = dp[i-1][j-a-c] + b + d
。 - 购买主件和第二个附件:
dp[i][j] = dp[i-1][j-a-e] + b + f
。 - 购买主件和两个附件:
dp[i][j] = dp[i-1][j-a-c-e] + b + d + f
。
- 只购买主件:
-
Math.max
用于比较多种购买方案,取其中的最大值。
6. 输出结果
System.out.println(dp[m][money] * 10);
- 最后输出
dp[m][money] * 10
,表示最大的满意度(乘以 10 恢复之前除以 10 的预算和价格)。
关键点:
- 主件和附件的组合:每个主件最多可以搭配两个附件。动态规划中要枚举主件及其附件的所有组合方式,确保每种组合都被考虑到。
- 动态规划的状态转移:四种购买组合分别考虑,保证在每个预算下取最大满意度。
示例解释:
假设输入:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
- 预算为 1000 元,物品有 5 个。
- 物品 1 是主件,价格 800,满意度 1600。
- 物品 2 和 3 是物品 1 的附件。
- 物品 4 和 5 是独立的主件。
通过动态规划计算,找到在预算内可获得的最大满意度组合,输出结果为 2200
。