字符串#
Java 字符串#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| import java.nio.charset.StandardCharsets;
import java.util.*;
public class Main {
public static void main(String[] args) {
String s1 = "hello"; // 字面量创建
String S3 = String.valueOf(123); // 任意类型转字符串
String s = "a" + "b" + 1; // "ab1" 少量拼接时使用
StringBuilder sb = new StringBuilder(); // 高频拼接使用
for (int i = 0; i < 5; i ++) {
sb.append(i).append(",");
}
String res = sb.toString();
// 拼接集合
s = String.join("-", "a", "b", "c"); // "a-b-c"
boolean a1 = s.isEmpty(); // 判空
int length = s.length(); // 长度
boolean a2 = s.startsWith("a"); // 开始
boolean a3 = s.contains("a"); // 包含
// 查找位置
int i = s.indexOf("ab"); // 查找
String sub = s.substring(1, 4); // 左闭右开
String r1 = s.replace("a", "x"); // 直接替换,非正则
String r2 = s.replaceAll("\\d+", "#"); // 正则替换
// 分割
String[] parts = "a,b,c".split(",");
// 格式化
String msg = String.format("id=%d, name=%s", 10, "tom");
// 字符编码
char c = s.charAt(0);
char[] arr = s.toCharArray();
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
String back = new String(bytes, StandardCharsets.UTF_8);
// 码点
s.codePoints().forEach(cp -> {
System.out.println(cp);
});
}
}
|
Go 字符串#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| func main() {
// 创建字符串
s1 := "hello"
fmt.Println(s1)
// 字符串长度
fmt.Println(len(s1))
// 少量拼接
s := "a" + "b" + "c";
fmt.Println(s)
// 大量拼接
var b strings.Builder
for i := 0; i < 10; i++ {
b.WriteString("a")
}
res := b.String()
fmt.Println(res)
// 拼接切片
parts := []string{"a", "b", "c"}
res = strings.Join(parts, "-")
fmt.Println(res)
// string byte 互转
bs := []byte("hello")
s := string(bs)
}
|
Python#
1
2
3
4
| if __name__ == '__main__':
s = "cbbc"
print(sorted(s)) // b b c c
print("".join(sorted(s))) // bbcc
|
1
2
3
4
5
| let s = "adbc"
let t = s.split("").sort() // a b c d
console.log(t)
let res = t.join("-") // a-b-c-d
console.log(res)
|
数组/列表#
Java 数组基本用法#
1
2
3
4
5
6
7
8
9
10
| public static void main(String[] args) {
int[] arr = {1, 2, 3};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
arr = new int[]{3, 4, 5};
for (int v : arr) {
System.out.println(v);
}
}
|
Java 有个奇怪的点就是可以使用字面量来在声明的位置初始化,但是在运行时其他位置时,只能使用 new int[] 这种格式来进行赋值。
访问和修改都可以通过下标 + [] 运算符的方式来赋值。
1
2
3
4
5
6
7
| public static void main(String[] args) {
int[] arr = {3, 2, 3};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
Arrays.fill(arr, 1);
System.out.println(Arrays.toString(arr));
}
|
arr 提供了一些便捷的工具可以用来对数组进行排序,填充值,打印等等。
在数组之上,java 提供了 List 接口,可以用来访问顺序数据结构,相对于数组,它不需要提前分配空间,而是需要在用的时候动态分配。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Integer[] arr = list.toArray(new Integer[0]);
list = new ArrayList<>(Arrays.asList(arr));
list.add(2);
list.add(1);
list.add(3);
System.out.println(list);
Collections.sort(list);
System.out.println(list);
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
Integer v = it.next();
if (v == 3) {
it.remove();
}
}
System.out.println(list);
list.set(1, 1);
System.out.println(list);
System.out.println(list.subList(0,1));
}
// output:
// [2, 1, 3]
// [1, 2, 3]
// [1, 2]
// [1]
|
在执行删除操作的时候,建议还是调用迭代器的方法进行安全删除。
另外,在使用 subList 截取 list 的某一个部分时,实际上是截取视图,如果想要深拷贝,建议使用 ArrayList 的构造函数再包一层。
Go 中的用法#
数组:
1
2
3
4
5
6
7
8
9
10
11
12
| var a [3]int // [0 0 0]
b := [3]int{1, 2, 3} // 指定长度
c := [...]int{1, 2, 3, 4} // 编译器推导长度
d := [5]int{1: 10, 3: 20} // 指定下标初始化 => [0 10 0 20 0]
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
fmt.Println(a == b) // true
m := map[[3]int]string{}
m[a] = "ok" // 注意: 切片不能作为 map 的 key
|
Go 中的数组其实用的不多,一般使用的还是切片 (slice)。
切片的底层实际上是对数组的视图,因此在传参的时候使用的不是值拷贝,而是引用拷贝。
和 java 不一样的是,定义切面时,[] 符号在类型前面。
1
2
3
4
5
6
7
8
9
10
11
| func main() {
arr := []int{1, 2, 3}
fmt.Println(arr)
arr = make([]int, len(arr))
arr = append(arr, 3)
fmt.Println(arr)
fmt.Println(arr[3:4])
}
// [1 2 3]
// [0 0 0 3]
// [3]
|
go 的切片操作和 python 类似,取值逻辑也是左闭右开。
在使用字面量进行初始化时,不能使用 {} 直接进行初始化,而是要在之前加上 []int。
从数组追加到切片:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:3] // 元素: [2,3], len=2, cap=4(从 arr[1] 到 arr[4])
fmt.Println(s, len(s), cap(s)) // [2 3] 2 4
s = append(s, 99) // cap 够,原地追加到 arr[3]
fmt.Println("s:", s) // [2 3 99]
fmt.Println("arr:", arr) // [1 2 3 99 5]
}
|
Python 的用法#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| if __name__ == '__main__':
list = [2]
list.append(4)
list.extend([3,1])
print(list)
list[2] = 3
print(list)
print(list[-1])
print(list[1:3])
for v in list:
print(v)
for i, v in enumerate(list):
print(i, v)
# 原地排序
v.sort()
# 不改变原有列表的排序
v2 = sorted(v)
squares = [x * x for x in range(5)]
print(squares)
# 浅拷贝
print(list[:])
# 步长
print(list[::2])
|
python 中有两个比较好的语法糖是其他语言中没有的:
- 列表推导式,可以快速生成一个规律的列表
- 切片,python 的切片非常灵活,可以指定步长
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| const arr = [1, 2];
arr.push(3); // [1,2,3]
arr.pop(); // 返回 3,arr -> [1,2]
arr.unshift(0); // [0,1,2]
arr.shift(); // 删除并返回 0
const arr = [1, 2, 3, 4];
// 删除:从 index=1 开始删 2 个
arr.splice(1, 2); // arr -> [1,4]
// 插入:从 index=1 插入
arr.splice(1, 0, 99); // arr -> [1,99,4]
// 替换:删 1 个再插入
arr.splice(2, 1, 7, 8);
const arr = [10, 20, 30];
// for
for (let i = 0; i < arr.length; i++) {}
// for...of(遍历值)
for (const x of arr) {}
// entries(索引+值)
for (const [i, x] of arr.entries()) {}
const arr = [1,2,3,4];
arr.slice(1, 3); // [2,3]
const arr = [10, 2, 1];
arr.sort(); // [1,10,2](字符串比较)
arr.sort((a, b) => a - b); // [1,2,10](数值升序)
|
Java 实现#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| import java.util.Arrays;
class Person {
String name;
int age;
int score;
Person(String name, int age, int score) {
this.name = name; this.age = age; this.score = score;
}
}
public class Demo {
public static void main(String[] args) {
Person[] arr = {
new Person("Tom", 20, 90),
new Person("Amy", 20, 95),
new Person("Bob", 18, 95)
};
// 规则:score 降序;score 相同 age 升序;再按 name 升序
Arrays.sort(arr, (p1, p2) -> {
int c = Integer.compare(p2.score, p1.score); // score 降序
if (c != 0) return c;
c = Integer.compare(p1.age, p2.age); // age 升序
if (c != 0) return c;
return p1.name.compareTo(p2.name); // name 升序
});
}
}
import java.util.*;
List<Integer> list = new ArrayList<>(Arrays.asList(5, 1, 3, 2));
list.sort((x, y) -> Integer.compare(y, x)); // 降序
System.out.println(list); // [5, 3, 2, 1]
|
Go 实现#
go 的排序依赖 sort 包实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
Score int
}
func main() {
ps := []Person{
{"Tom", 20, 90},
{"Amy", 20, 95},
{"Bob", 18, 95},
}
// 规则:Score 降序;Score 相同 Age 升序;再按 Name 升序
sort.Slice(ps, func(i, j int) bool {
if ps[i].Score != ps[j].Score {
return ps[i].Score > ps[j].Score // 降序
}
if ps[i].Age != ps[j].Age {
return ps[i].Age < ps[j].Age // 升序
}
return ps[i].Name < ps[j].Name
})
fmt.Println(ps)
}
|
Java 实现#
在 Java 中,常用的队列是 LinkedList
对我而言,offer(提供)/poll(轮询)/peek 相对来说比较难记,我更适应其他语言的 push/pop/top 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import java.util.*;
public class Main {
public static void main(String[] args) {
Deque<Integer> deque = new LinkedList<>();
// 失败时返回 false
deque.offer(2); // 等价 offerLast
deque.offer(3); // 2 3
System.out.println(deque.peek()); // 2 等价 peekFirst
System.out.println(deque.peekLast());
deque.poll();
System.out.println(deque); // 3
// 栈相关方法
deque.push(6); // 等价 offerFirst
deque.push(7);
System.out.println(deque); // 7 6 3
}
}
|
Go 实现#
Go 中,可以通过引入 container/list 包来支持队列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| func main() {
dq := list.New()
// 入队:两端都可以
dq.PushBack(1) // 尾部入
dq.PushFront(0) // 头部入
dq.PushBack(2)
// 查看头/尾(不删除)
front := dq.Front().Value.(int)
back := dq.Back().Value.(int)
fmt.Println("front =", front, "back =", back)
// 出队:两端都可以
v1 := dq.Remove(dq.Front()).(int) // 头部出
v2 := dq.Remove(dq.Back()).(int) // 尾部出
fmt.Println(v1, v2)
fmt.Println("len =", dq.Len())
}
|
Python#
在 python 中,可以导入 collections/deque 来实现队列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| from collections import deque
if __name__ == '__main__':
dq = deque()
dq.append(1) # 右端入队
dq.appendleft(0) # 左端入队
dq.append(2)
print(dq) # deque([0, 1, 2])
x = dq.popleft() # 左端出队 -> 0
y = dq.pop() # 右端出队 -> 2
print(x, y, dq) # 0 2 deque([1])
|
js 没有高效的队列实现,建议自己使用 list 实现环形队列。
优先级队列#
Java#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import java.util.PriorityQueue;
public class Demo {
public static void main(String[] args) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(1);
pq.offer(3);
System.out.println(pq.peek()); // 1(查看最小值,不删除)
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // 1, 3, 5(依次弹出最小)
}
}
}
|
自定义排序规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| import java.util.PriorityQueue;
class Task {
int priority;
String name;
Task(int priority, String name) {
this.priority = priority;
this.name = name;
}
}
public class Demo2 {
public static void main(String[] args) {
PriorityQueue<Task> pq = new PriorityQueue<>(
(t1, t2) -> Integer.compare(t1.priority, t2.priority)
);
pq.offer(new Task(2, "B"));
pq.offer(new Task(1, "A"));
pq.offer(new Task(3, "C"));
while (!pq.isEmpty()) {
Task t = pq.poll();
System.out.println(t.priority + " " + t.name);
}
}
}
|
HashMap#
Hashmap 基本用法#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 新增/更新
map.put("apple", 3);
map.put("banana", 5);
// 覆盖 apple 的值
map.put("apple", 2);
// 查询
// 存在的值
System.out.println(map.get("apple"));
// 不存在的值
System.out.println(map.get("pea"));
// 判断是否存在
System.out.println(map.containsKey("apple"));
// 删除
System.out.println(map.remove("apple"));
// 安全查询
int cnt = map.getOrDefault("pea", 0);
System.out.println(cnt);
}
}
// output
// 2
// null
// true
// 2
// 0
|
Java 的容器类往往放在 java.util 包中。
Java 中用 put 来往容器中设置值,为什么使用 put:
- put 在 Map 中代表“放入/放置“的意思,就是将一个 k-v 关系放入映射结构中。
- 有一个 put 的小知识点,就是 put 会返回设置之前的旧值
- put 会替换掉之前已经设置的值,没有就插入,有就替换。
- 为什么不用 set,set 只有赋值,但是不涉及新增,它是对已有字段的修改
- 为什么不用 add/insert,因为他们的语义是只添加不覆盖。
Java 只能用 get 系列方法从 map 中取值,像其他语言中直接使用 [] 的方式是不合法的,这一点就很不方便。
Java 对于集合类的常见操作就是用接口访问,实际上这个是推荐的方式,在项目中,也应该先定义接口,用接口去访问对象信息。
关于 map 的操作,put/get/containsKey/remove 就最为常见,使用这几个方法能解决大部分问题。
map 接口#
实际使用中,往往采用 map 接口访问对应的 map 实现类。
map 中接口中有两个方法很常用,分别是 size() 和 isEmpty(),这两个方法经常被用作判空,但是要注意空指针问题。
1
2
3
| int size();
boolean isEmpty();
|
map 接口中,关于 put 方法的定义可以看作泛型使用的教科书:
1
2
3
| public interface Map<K,V> {
V put(K key, V value);
}
|
如果想在一个 map 中完全 put 另外一个 map 的值,可以使用 putAll 方法,这个也挺常用,不用自己写 for 循环了:
1
| void putAll(Map<? extends K, ? extends V> m);
|
如果想要遍历 k-v 对,map 提供了三种可以用到的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 新增/更新
map.put("apple", 3);
map.put("banana", 5);
// 覆盖 apple 的值
map.put("apple", 2);
for (String key : map.keySet()) {
System.out.println(key);
}
for (Integer value : map.values()) {
System.out.println(value);
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
// Output:
// banana
// apple
// 5
// 2
// banana:5
// apple:2
|
Go 中的 map#
Go 中的 map 构建相对简单些,通常,我们会使用下面的写法 2,需要注意的是在使用字面量初始化的时候,不需要带 make,以及所有的 k-v 对,包括最后一个 k-v 对都要加 , 结尾。
1
2
3
4
5
6
7
8
9
10
11
| func main() {
// 写法 1
var mp map[string]int
mp = make(map[string]int)
// 写法 2
mp2 := make(map[string]int)
mp3 := map[string]int {
"test": 3,
"test4": 3,
}
}
|
和 Java 相比,更新/访问 map 中的某个值不需要通过繁琐的 put/get 方法,只需要用 [] 运算符即可完成,同时遍历 map 中的元素也只需使用简单的 for-range 语法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| func main() {
mp := make(map[string]int)
mp["apple"] = 3
mp["banana"] = 5
mp["apple"] = 2
fmt.Println(mp["apple"])
_, ok := mp["apple"]
fmt.Println(ok)
for k, v := range mp {
fmt.Println(k, v)
}
}
// Output:
// 2
// true
// apple 2
// banana 5
|
Go 中另外一个常见的用法是将 map[string]struct{} 结构用作集合,因为 Go 中并没有对应 Java 中的 Set 结构,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
| func main() {
set := make(map[string]interface{})
set["a"] = struct{}{}
_, ok := set["a"]
fmt.Println(ok)
delete(set, "a")
_, ok = set["a"]
fmt.Println(ok)
}
// Output:
// true
// false
|
Python 中的 map#
1
2
3
4
5
6
7
8
9
10
11
12
| if __name__ == '__main__':
d = {"apple": 5, "banana": 4}
d["apple"] = 3
print(d["banana"])
print(d.get("pea", 0))
print("pea" in d)
for k, v in d.items():
print(k, v)
print(d.keys())
print(d.values())
|
python 中的 map 结构被称为 dict,dict 和 go 一样,可以通过 [] 运算符来判断,但是需要注意的是,如果值不存在,会抛出异常,所以更推荐使用 get 来为缺省值提供默认值。
dict 可以使用 for-in 语法进行遍历,这可以参考 go 中的 for-range 语法。
js 中的 map#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| var m = new Map()
m.set("banana", 1)
m.set("apple", 2)
console.log(m.get("banana"))
console.log(m.has("key"))
m = new Map([
["banana", 3],
])
console.log(m.get("banana"))
for (const [k, v] of m) {
console.log(k, v)
}
|
js 中,可以通过 new Map 来创建一个 map,和 java 类似,需要通过 get/set 方法来设置值,不过 Java 用的 put,js 用的 set。
有点特殊的是,js 需要通过数组的字面量形式来初始化 map。
有些情况下,m.get("pea") === undefined 可以取代 has,但是要注意,如果存在 pea,且 pea 的值就是 undefined 则无法取代。
1
2
3
4
5
6
7
| let mp = new Map()
mp.set('a', 1)
console.log(mp.values());
console.log(Array.from(mp.values()));
// [Map Iterator] { 1 }
// [ 1 ]
|
需要注意的是,values 返回的是一个迭代器,而不是真正的 array,需要zhuan
Map 的 LeetCode#
这道算法的核心就是空间换时间,如果不使用 map 数据结构,需要通过两层 for 循环来找到最终的答案,使用 map 数据结构可以快速找到上一个和这个元素互补(和为 target)的元素的位置。
这道题涉及的 Java 的几个基本语法有:
- 初始化一个 map
- for 循环遍历数组
- 返回一个数组
- map 的 put 和 get 用法
- 判断是否存在某个 key
1
2
3
4
5
6
7
8
9
10
11
12
| class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i ++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target-nums[i]), i};
}
map.put(nums[i], i);
}
return new int[]{0,0};
}
}
|
Go 语言也是类似,无非是判断 key 存在的方式更加简洁了一点。
1
2
3
4
5
6
7
8
9
10
11
| func twoSum(nums []int, target int) []int {
mp := make(map[int]int, 0)
for i, v := range nums {
targetV := target - v
if targetI, ok := mp[targetV]; ok {
return []int{targetI, i}
}
mp[v] = i
}
return []int{0, 0}
}
|
1
2
3
4
5
6
7
8
9
| class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
mp = {}
for i, v in enumerate(nums):
targetV = target - v
if targetV in mp:
return [i, mp[targetV]]
mp[v] = i
return [0,0]
|
1
2
3
4
5
6
7
8
9
10
11
12
| var twoSum = function(nums, target) {
mp = new Map();
for (let i = 0; i < nums.length; i ++) {
let num = nums[i];
let targetNum = target - num;
if (mp.has(targetNum)) {
return [mp.get(targetNum), i]
}
mp.set(num, i);
}
return [0,0]
};
|