牛客华为算法解题及相关知识点汇总(1-15)

数据结构
java自带数据结构框架
string常用方法
scanner常用方法
arraylist常用方法

算法工具

反转

(需要完整的列表内容,while(in.hasnext()),就会不能确认,反转不了)
Collctions.reverse(list)//反转list中内容

去重

在 Java 中,有多种便捷的方法可以去除集合中的重复元素。最常见和快捷的方法是使用 Set 数据结构,因为 Set 不允许有重复的元素。Java 提供了几个 Set 实现类,如 HashSetLinkedHashSetTreeSet,这些类都可以方便地用于去重操作。

下面是几种常见的去重方式:

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 去重并自动排序

如果你需要去重并按照自然顺序(或自定义排序规则)对元素排序,可以使用 TreeSetTreeSet 基于红黑树实现,添加元素时会自动排序。

示例代码:
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() 方法将结果转换回 ListSet

示例代码:
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. 手动遍历去重

如果你不想使用 SetStream,可以手动通过遍历实现去重。通常使用一个辅助的 ListSet 来存储已经遇到的元素,跳过重复项。

示例代码:
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]

总结

  1. HashSet 是最常用且效率较高的去重方法,但不保留元素的插入顺序。
  2. LinkedHashSet 可以在去重的同时保留元素的插入顺序。
  3. TreeSet 去重并对元素自动排序(自然顺序或自定义顺序)。
  4. Stream APIdistinct() 是 Java 8 及以上版本中较为简洁的去重方法。
  5. 手动遍历去重 适用于需要自定义逻辑的场景,但效率较低。

根据具体需求,你可以选择不同的去重方式。如果追求效率和简洁,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());
    }
}

主要概念:

  1. replaceAll(String regex, String replacement)
    • 该方法用于替换字符串中所有匹配指定正则表达式 (regex) 的子字符串,并用给定的替换字符串 (replacement) 替换它们。
    • 在这个例子中,代码将 input1 中所有与 input2 相同的字符(不区分大小写)替换为空字符串(即删除),并将替换后的新字符串存储在 split3 中。

实现逻辑:

  1. 用户输入一个字符串 input1 和一个字符或子字符串 input2
  2. input1.toUpperCase()input2.toUpperCase() 将输入转换为大写形式,确保在比较时不区分大小写。
  3. 使用 replaceAll 将所有 input1 中与 input2 相同的字符删除。
  4. 原始字符串的长度减去删除后的字符串的长度,就得到了该字符在字符串中出现的次数。

示例:

如果输入:

  • 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 在其中的作用。

代码逻辑简要:

  1. 输入读取:使用 Scanner 获取用户输入。
  2. 字符串操作
    • 创建 StringBuilder 对象用于拼接输入的字符串和填充的 ‘0’。
    • 如果输入的字符串长度不是 8 的倍数,就在字符串末尾添加 0,直到长度变为 8 的倍数。
  3. 分段输出:将处理后的字符串每 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

  1. 高效拼接字符串

    • 在 Java 中,字符串 String 是不可变的(immutable),每次对 String 执行操作(如拼接、修改等)都会生成新的字符串对象,旧的对象会被丢弃。这种操作在需要大量修改字符串的场景下效率低下。
    • StringBuilder 是可变的字符串类,允许我们对原字符串进行修改,而不需要创建新的字符串对象。它通过内部可调整的字符数组实现这一点,极大提高了字符串拼接的效率。
  2. 避免创建过多中间对象

    • 在这段代码中,每次对字符串追加 ‘0’ 的操作,如果使用普通的 String,每次操作都要创建一个新的字符串对象,效率非常低。而 StringBuilder 能直接在原字符串基础上修改,不需要额外创建对象,减少了内存占用和垃圾回收的压力。
  3. 用于构建和处理可变字符串

    • 通过 StringBuilderappend() 方法,可以灵活地在现有的字符串后追加任意内容,操作简单且高效。
    • 最终通过 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 的末尾追加内容。可以追加各种类型的内容(Stringintcharboolean 等)。

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));
        }
    }
}

核心实现:

  1. substring(2, s.length())

    • 由于输入的十六进制数通常以 “0x” 开头(如 “0xAA”),所以通过 substring() 方法跳过前两个字符,只保留真正的十六进制数部分。
    • 例如,“0xAA” 会被截取为 “AA”。
  2. Integer.parseInt(s, 16)

    • Integer.parseInt() 方法可以将字符串转换为整数,并且允许指定进制。
    • parseInt(String s, int radix):这里的 radix16,表示输入的字符串是一个十六进制数。
    • 例如,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_VALUEint 类型的最大值(2^31 - 1,即 2147483647)。
  • Integer.MIN_VALUEint 类型的最小值(-2^31,即 -2147483648)。
8. 自动装箱与拆箱
  • Integerint 的包装类,在需要 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

总结:

  1. Integer.parseInt() 是将字符串解析为整数的常用方法,可以解析不同进制的数字字符串。
  2. Integer 类提供了很多静态方法来处理 int 类型的数据,如数字转换、比较、解析字符串等。
  3. 自动装箱与拆箱是 Integer 类与基本数据类型 int 之间的一种方便操作,可以在需要 intInteger 时自动完成转换。

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 的遍历方式:

  1. table.keySet()

    • keySet() 方法返回 Map 中所有键的一个 Set 视图。
    • Set 中的每个键都是唯一的(因为 Map 中的键不能重复),所以我们通过遍历这个键集合来访问 Map 中的每个键值对。
  2. for (Integer key : table.keySet())

    • 这是一个增强的 for 循环,它遍历 table.keySet(),即 Map 中所有的键。
    • 对于每个键,key 变量会依次被赋予 keySet() 中的值。
  3. table.get(key)

    • get() 方法用于根据键获取对应的值。
    • 在循环中,对于每个键,使用 table.get(key) 获取它对应的值。

完整遍历的过程:

  1. 获取键的集合:通过 table.keySet() 方法,获取 Map 中所有键的集合。
  2. 循环遍历键集合:对于每一个键,通过 for 循环进行遍历。
  3. 获取对应值:在循环体内,通过 table.get(key) 方法,获取该键对应的值。
  4. 输出结果:将键和值组合输出。

例子:

假设输入如下(两个键值对 1 102 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);
    }
}

StringBuilderStringBuffer 都是 Java 中用于创建和操作可变字符串的类,它们的主要区别在于 线程安全性性能,此外它们的方法也非常相似,主要提供对字符串进行修改的功能。

1. StringBuilderStringBuffer 的区别

线程安全性:
  • StringBuffer:是线程安全的。它的所有方法都被 synchronized 修饰,意味着多个线程可以安全地访问同一个 StringBuffer 实例,而不会引发并发问题。
  • StringBuilder不是线程安全的。它没有提供线程同步机制,因此在单线程环境下或者不需要线程安全的情况下使用效率更高。
性能:
  • StringBuilder:因为没有线程安全机制,性能比 StringBuffer 高。在单线程操作可变字符串时,StringBuilder 是首选。
  • StringBuffer:由于提供了线程同步机制,性能会稍逊于 StringBuilder。它适合在多线程环境下使用。

2. 相同点

  • 可变性:与 String 类不同,StringBuilderStringBuffer 都允许对字符串进行修改,不会像 String 那样每次修改都创建新的对象。
  • 相同的 APIStringBuilderStringBuffer 拥有几乎相同的方法,它们提供了非常类似的功能,包括追加、插入、删除、反转等操作。

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
  • StringBuilderStringBuffer 对象转换为 String
StringBuilder sb = new StringBuilder("Hello");
String str = sb.toString();
System.out.println(str);  // 输出: Hello
9. length()capacity()
  • length():获取当前字符串的长度。
  • capacity():获取当前 StringBuilderStringBuffer 对象的容量。
StringBuilder sb = new StringBuilder("Hello");
System.out.println(sb.length());    // 输出: 5
System.out.println(sb.capacity());  // 输出: 默认容量 16 + 字符串长度
10. ensureCapacity():确保容量
  • 确保 StringBuilderStringBuffer 的容量至少为给定的值。
StringBuilder sb = new StringBuilder();
sb.ensureCapacity(50);  // 确保容量至少为 50

4. 性能比较

在单线程环境中,StringBuilder 是性能较优的选择。因为它没有额外的同步机制,速度更快,尤其是在频繁对字符串进行修改时。

在多线程环境中,如果多个线程可能同时访问同一个字符串对象,则应该使用 StringBuffer 来确保线程安全。

5. 选择指南

  • StringBuilder:适合在单线程环境下使用,或者在不需要线程同步的场景中。一般情况下,推荐使用 StringBuilder,因为它的性能更好。
  • StringBuffer:适合在多线程环境下使用,尤其是当多个线程需要共享和修改同一个字符串时,应该选择 StringBuffer,因为它提供了线程安全性。

总结

特性StringBuilderStringBuffer
线程安全
性能更高(因为没有同步机制)较低(同步开销较大)
适用场景单线程环境多线程环境
主要方法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背包问题,涉及到主件和附件的购买。我们需要根据预算和物品的依赖关系,选择能够带来最大“满意度”的组合。你的代码解决了这个问题,具体做法是通过动态规划来处理,并考虑了主件和附件的不同组合方式。下面我们详细解析这个代码的实现逻辑。

问题解析

  1. 输入

    • N:预算,代表可以花的钱数。
    • m:物品的数量。
    • 每个物品有价格 v,重要度 p,以及一个标志 q,表示是主件还是附件。
  2. 主件和附件关系

    • 主件可以单独购买。
    • 附件必须与主件一起购买。
    • 每个主件最多有两个附件。
  3. 目标

    • 最大化购买的“满意度”,即 价格 * 重要度 的和。

代码解析

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 元时的最大满意度。

  • 主要的动态规划方程考虑了以下几种情况:

    1. 只购买主件:dp[i][j] = dp[i-1][j-a] + b
    2. 购买主件和第一个附件:dp[i][j] = dp[i-1][j-a-c] + b + d
    3. 购买主件和第二个附件:dp[i][j] = dp[i-1][j-a-e] + b + f
    4. 购买主件和两个附件: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 的预算和价格)。

关键点:

  1. 主件和附件的组合:每个主件最多可以搭配两个附件。动态规划中要枚举主件及其附件的所有组合方式,确保每种组合都被考虑到。
  2. 动态规划的状态转移:四种购买组合分别考虑,保证在每个预算下取最大满意度。

示例解释:

假设输入:

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

动态规划详解:动态规划

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值