跳转至

Java 進階

注意

以下內容均基於 Java JDK 8 版本編寫,不排除在更高版本中有部分改動的可能性。

更高速的輸入輸出

ScannerSystem.out.print 在最開始會工作得很好,但是在處理更大的輸入的時候會降低效率,因此我們會需要使用一些方法來提高 IO 速度。

使用 Kattio + StringTokenizer 作為輸入

最常用的方法之一是使用來自 Kattis 的 Kattio.java 來提高 IO 效率。1這個方法會將 StringTokenizerPrintWriter 包裝在一個類中方便使用。而在具體進行解題的時候(假如賽會/組織方允許)可以直接使用這個模板。

下方即為應包含在代碼中的 IO 模板,由於 Kattis 的原 Kattio 包含一些並不常用的功能,下方的模板經過了一些調整(原 Kattio 使用 MIT 作為協議)。

 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
class Kattio extends PrintWriter {
    private BufferedReader r;
    private StringTokenizer st;
    // 標準 IO
    public Kattio() { this(System.in, System.out); }
    public Kattio(InputStream i, OutputStream o) {
        super(o);
        r = new BufferedReader(new InputStreamReader(i));
    }
    // 文件 IO
    public Kattio(String intput, String output) throws IOException {
        super(output);
        r = new BufferedReader(new FileReader(intput));
    }
    // 在沒有其他輸入時返回 null
    public String next() {
        try {
            while (st == null || !st.hasMoreTokens())
                st = new StringTokenizer(r.readLine());
            return st.nextToken();
        } catch (Exception e) {}
        return null;
    }
    public int nextInt() { return Integer.parseInt(next()); }
    public double nextDouble() { return Double.parseDouble(next()); }
    public long nextLong() { return Long.parseLong(next()); }
}

而下方代碼簡單展示了 Kattio 的使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Test {
    public static void main(String[] args) {
        Kattio io = new Kattio();
        // 字符串輸入
        String str = io.next();
        // int 輸入
        int num = io.nextInt();
        // 輸出
        io.println("Result");
        // 請確保關閉 IO 流以確保輸出被正確寫入
        io.close();
    }
}

使用 StreamTokenizer 作為輸入

在某些情況使用 StringTokenizer 會導致 MLE(Memory Limit Exceeded,超過內存上限),此時我們需要使用 StreamTokenizer 作為輸入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import java.io.*;
public class Main {
    // IO 代碼
    public static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in), 32768));
    public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    public static double nextDouble() throws IOException { in.nextToken(); return in.nval; }
    public static float nextFloat() throws IOException { in.nextToken(); return (float)in.nval; }
    public static int nextInt() throws IOException { in.nextToken(); return (int)in.nval; }
    public static String next() throws IOException { in.nextToken(); return in.sval; }
    public static long nextLong() throws Exception { in.nextToken(); return (long)in.nval;}
    
    // 使用示例
    public static void main(String[] args) throws Exception {
        int n = nextInt();
        out.println(n);
        out.close();
    }
}

Kattio + StringTokenizer 的方法與 StreamTokenizer 的方法之間的分析與對比

  1. StreamTokenizer 相較於 StringTokenizer 使用的內存較少,當 Java 標程 MLE 時可以嘗試使用 StreamTokenizer,但是 StreamTokenizer 會丟失精度,讀入部分數據時會出現問題;
    • StreamTokenizer 源碼存在 Type,該 Type 根據你輸入內容來決定類型,倘若你輸入類似於 123oi數字開頭 的字符串,他會強制認為你的類型是 double 類型,因此在讀入中以 double 類型去讀 String 類型便會拋出異常;
    • StreamTokenizer 在讀入 1e14 以上大小的數字會丟失精度;
  2. 在使用 PrintWriter 情況下,需注意在程序結束最後 close() 關閉輸出流或在需要輸出的時候使用 flush() 清除緩衝區,否則內容將不會被寫入到控制枱/文件中。
  3. Kattio 是繼承自 PrintWriter 類,自身對象具有了 PrintWriter 的功能,因此可以直接調用 PrintWriter 類的函數輸出,同時將 StringTokenizer 作為了自身的成員變量來修改。而第二種 Main 是同時將 StreamTokenizerPrintWriter 作為了自身的成員變量,因此在使用上有些許差距。

綜上所述,在大部分情況下,StringTokenizer 的使用處境要優越於 StreamTokenizer,在極端 MLE 的情況下可以嘗試 StreamTokenizer,同時 int 範圍以上的數據 StreamTokenizer 處理是無能為力的。

BigInteger 與數論

BigInteger 是 Java 提供的高精度計算類,可以很方便地解決高精度問題。

初始化

BigInteger 常用創建方式有如下二種:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import java.io.PrintWriter;
import java.math.BigInteger;

class Main {
    static PrintWriter out = new PrintWriter(System.out);
    public static void main(String[] args) {
        BigInteger a = new BigInteger("12345678910");  // 將字符串以十進制的形式創建 BigInteger 對象
        out.println(a);  // a 的值為 12345678910 
        BigInteger b = new BigInteger("1E", 16);  // 將字符串以指定進制的形式創建 BigInteger 對象
        out.println(b);  // b 的值為 30 
        out.close();
    }
}

基本運算

以下均用 this 代替當前 BigIntger :

函數名 功能
abs() 返回 this 的絕對值
negate() 返回 - this
add(BigInteger val) 返回 this + val
subtract(BigInteger val) 返回 this - val
multiply(BigInteger val) 返回 this * val
divide(BigInteger val) 返回 this / val
remainder(BigInteger val) 返回 this % val
mod(BigInteger val) 返回 this mod val
pow(int e) 返回 \(this^e\)
and(BigInteger val) 返回 this & val
or(BigInteger val) 返回 this | val
not() 返回 ~ this
xor(BigInteger val) 返回 this ^ val
shiftLeft(int n) 返回 this << n
shiftRight(int n) 返回 this >> n
max(BigInteger val) 返回 this 與 val 的較大值
min(BigInteger val) 返回 this 與 val 的較小值
bitCount() 返回 this 的二進制中不包括符號位的 1 的個數
bitLength() 返回 this 的二進制中不包括符號位的長度
getLowestSetBit() 返回 this 的二進制中最右邊的位置
compareTo(BigInteger val) 比較 this 和 val 值大小
toString() 返回 this 的 10 進制字符串表示形式
toString(int radix) 返回 this 的 raidx 進制字符串表示形式

使用案例如下:

  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
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import java.io.PrintWriter;
import java.math.BigInteger;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static BigInteger a, b;

    static void abs() {
        out.println("abs:");
        a = new BigInteger("-123");
        out.println(a.abs());  // 輸出 123 
        a = new BigInteger("123");
        out.println(a.abs());  // 輸出 123 
    }

    static void negate() {
        out.println("negate:");
        a = new BigInteger("-123");
        out.println(a.negate());  // 輸出 123 
        a = new BigInteger("123");
        out.println(a.negate());  // 輸出 -123 
    }

    static void add() {
        out.println("add:");
        a = new BigInteger("123");
        b = new BigInteger("123");
        out.println(a.add(b));  // 輸出 246 
    }

    static void subtract() {
        out.println("subtract:");
        a = new BigInteger("123");
        b = new BigInteger("123");
        out.println(a.subtract(b));  // 輸出 0 
    }

    static void multiply() {
        out.println("multiply:");
        a = new BigInteger("12");
        b = new BigInteger("12");
        out.println(a.multiply(b));  // 輸出 144 
    }

    static void divide() {
        out.println("divide:");
        a = new BigInteger("12");
        b = new BigInteger("11");
        out.println(a.divide(b));  // 輸出 1 
    }

    static void remainder() {
        out.println("remainder:");
        a = new BigInteger("12");
        b = new BigInteger("10");
        out.println(a.remainder(b));  // 輸出 2 
        a = new BigInteger("-12");
        b = new BigInteger("10");
        out.println(a.remainder(b));  // 輸出 -2 
    }

    static void mod() {
        out.println("mod:");
        a = new BigInteger("12");
        b = new BigInteger("10");
        out.println(a.mod(b));  // 輸出 2 
        a = new BigInteger("-12");
        b = new BigInteger("10");
        out.println(a.mod(b));  // 輸出 8 
    }

    static void pow() {
        out.println("pow:");
        a = new BigInteger("2");
        out.println(a.pow(10));  // 輸出 1024 
    }

    static void and() {
        out.println("and:");
        a = new BigInteger("3");  // 11 
        b = new BigInteger("5");  // 101 
        out.println(a.and(b));  // 輸出 1 
    }

    static void or() {
        out.println("or:");
        a = new BigInteger("2");  // 10 
        b = new BigInteger("5");  // 101 
        out.println(a.or(b));  // 輸出 7 
    }

    static void not() {
        out.println("not:");
        a = new BigInteger("2147483647");  // 01111111 11111111 11111111 11111111 
        out.println(a.not());  // 輸出 -2147483648 二進制為:10000000 00000000 00000000 00000000 
    }

    static void xor() {
        out.println("xor:");
        a = new BigInteger("6");  // 110 
        b = new BigInteger("5");  // 101 
        out.println(a.xor(b));  // 011 輸出 3 
    }

    static void shiftLeft() {
        out.println("shiftLeft:");
        a = new BigInteger("1");
        out.println(a.shiftLeft(10));  // 輸出 1024 
    }

    static void shiftRight() {
        out.println("shiftRight:");
        a = new BigInteger("1024");
        out.println(a.shiftRight(8));  // 輸出 4 
    }

    static void max() {
        out.println("max:");
        a = new BigInteger("6");
        b = new BigInteger("5");
        out.println(a.max(b));  // 輸出 6 
    }

    static void min() {
        out.println("min:");
        a = new BigInteger("6");
        b = new BigInteger("5");
        out.println(a.min(b));  // 輸出 5 
    }

    static void bitCount() {
        out.println("bitCount:");
        a = new BigInteger("6");  // 110 
        out.println(a.bitCount());  // 輸出 2 
    }

    static void bitLength() {
        out.println("bitLength:");
        a = new BigInteger("6");  // 110 
        out.println(a.bitLength());  // 輸出 3 
    }

    static void getLowestSetBit() {
        out.println("getLowestSetBit:");
        a = new BigInteger("8");  // 1000 
        out.println(a.getLowestSetBit());  // 輸出 3 
    }

    static void compareTo() {
        out.println("compareTo:");
        a = new BigInteger("8");
        b = new BigInteger("9");
        out.println(a.compareTo(b));  // 輸出 -1 
        a = new BigInteger("8");
        b = new BigInteger("8");
        out.println(a.compareTo(b));  // 輸出 0 
        a = new BigInteger("8");
        b = new BigInteger("7");
        out.println(a.compareTo(b));  // 輸出 1 
    }

    static void toStringTest() {
        out.println("toString:");
        a = new BigInteger("15");
        out.println(a.toString());  // 輸出 15 
        out.println(a.toString(16));  // 輸出 f 
    }

    public static void main(String[] args) {
        abs();
        negate();
        add();
        subtract();
        multiply();
        divide();
        remainder();
        mod();
        pow();
        and();
        or();
        not();
        xor();
        shiftLeft();
        shiftRight();
        max();
        min();
        bitCount();
        bitLength();
        getLowestSetBit();
        compareTo();
        toStringTest();
        out.close();
    }
}

數學運算

以下均用 this 代替當前 BigIntger :

函數名 功能
gcd(BigInteger val) 返回 this 的絕對值與 val 的絕對值的最大公約數
isProbablePrime(int val) 返回一個表示 this 是否是素數的布爾值
nextProbablePrime() 返回第一個大於 this 的素數
modPow(BigInteger b, BigInteger p) 返回 this ^ b mod p
modInverse(BigInteger p) 返回 a mod p 的乘法逆元

使用案例如下:

 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.io.PrintWriter;
import java.math.BigInteger;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static BigInteger a, b, p;

    static void gcd() {  // 最大公約數 
        a = new BigInteger("120032414321432144212100");
        b = new BigInteger("240231431243123412432140");
        out.println(String.format("gcd(%s,%s)=%s", a.toString(), b.toString(), a.gcd(b).toString()));  // gcd(120032414321432144212100,240231431243123412432140)=20 
    }

    static void isPrime() {  // 基於米勒羅賓判定該數是否是素數,參數越大準確性越高,複雜度越高。準確性為 (1-1/(val*2)) 
        a = new BigInteger("1200324143214321442127");
        out.println("a:" + a.toString());
        out.println(a.isProbablePrime(10) ? "a is prime" : "a is not prime");  // a is not prime 
    }

    static void nextPrime() {  // 找出該數的下一個素數 
        a = new BigInteger("1200324143214321442127");
        out.println("a:" + a.toString());
        out.println(String.format("a nextPrime is %s", a.nextProbablePrime().toString()));  // a nextPrime is 1200324143214321442199 
    }

    static void modPow() {  // 快速冪,比正常版本要快,內部有數學優化 
        a = new BigInteger("2");
        b = new BigInteger("10");
        p = new BigInteger("1000");
        out.println(String.format("a:%s b:%s p:%s", a, b, p));
        out.println(String.format("a^b mod p:%s", a.modPow(b, p).toString()));//  24 
    }

    static void modInverse() {  // 逆元 
        a = new BigInteger("10");
        b = new BigInteger("3");
        out.println(a.modInverse(b));  // a ^ (p-2) mod p = 1 
    }

    public static void main(String[] args) {
        gcd();
        isPrime();
        nextPrime();
        modPow();
        modInverse();
        out.close();
    }
}

關於米勒羅賓相關知識可以查閲Miller–Rabin 素性測試

基本數據類型與包裝數據類型

簡介

由於基本類型沒有面向對象的特徵,為了他們參加到面向對象的開發中,Java 為八個基本類型提供了對應的包裝類,分別是 ByteDoubleFloatIntegerLongShortCharacterBoolean。兩者之間的對應關係如下:

基本數據類型 包裝數據類型
byte Byte
short Short
boolean Boolean
char Character
int Integer
long Long
float Float
double Double

區別

此處以 intInteger 舉例:

  1. Integerint 的包裝類,int 則是 Java 的一種基本類型數據。
  2. Integer 類型實例後才能使用,而 int 類型不需要。
  3. Integer 實際對應的引用,當 new 一個 Integer 時,實際上生成了一個對象,而 int 則是直接存儲數據。
  4. Integer 的默認值是 null,可接受 nullint 類型的數據, int 默認值是 0,不能接受 null 類型的數據。
  5. Integer 判定二個變量是否相同使用 == 可能會導致不正確的結果,只能使用 equals(),而 int 可以直接使用 ==

裝箱與拆箱

此處以 intInteger 舉例:

Integer 的本質是對象,int 是基本類型,兩個類型之間是不能直接賦值的。需要轉換時,應將基礎類型轉換為包裝類型,這種做法稱為裝箱,反過來則稱為拆箱。

1
2
3
4
5
6
// 基本類型
int value1 = 1;
// 裝箱轉換為包裝類型
Integer integer = Integer.valueOf(value1);
// 拆箱轉換為基本類型
int value2 = integer.intValue();

Java 5 引入了自動裝箱拆箱機制:

1
2
Integer integer = 1;
int value = integer;
注意

雖然 JDK 增加了自動裝箱拆箱的機制,但在聲明變量時請選擇合適的類型,因為包裝類型 Integer 可以接受 null,而基本類型 int 不能接受 null。因此,對使用 null 值的包裝類型進行拆箱操作時,會拋出異常。

1
2
3
4
5
Integer integer = Integer.valueOf(null);
integer.intValue();  // 拋出 java.lang.NumberFormatException 異常

Integer integer = null;
integer.intValue();  // 拋出 java.lang.NullPointerException 異常

繼承

基於已有的設計創造新的設計,就是面向對象程序設計中的繼承。在繼承中,新的類不是憑空產生的,而是基於一個已經存在的類而定義出來的。通過繼承,新的類自動獲得了基礎類中所有的成員,包括成員變量和方法,包括各種訪問屬性的成員,無論是 public 還是 private 。顯然,通過繼承來定義新的類,遠比從頭開始寫一個新的類要簡單快捷和方便。繼承是支持代碼重用的重要手段之一。

在 Java 中,繼承的關鍵字為 extends,且 Java 只支持單繼承,但可以實現多接口。

在 Java 中,所有類都是 Object 類的子類。

子類繼承父類,所有的父類的成員,包括變量和方法,都成為了子類的成員,除了構造方法。構造方法是父類所獨有的,因為它們的名字就是類的名字,所以父類的構造方法在子類中不存在。除此之外,子類繼承得到了父類所有的成員。

每個成員有不同的訪問屬性,子類繼承得到了父類所有的成員,但是不同的訪問屬性使得子類在使用這些成員時有所不同:有些父類的成員直接成為子類的對外的界面,有些則被深深地隱藏起來,即使子類自己也不能直接訪問。

下表列出了不同訪問屬性的父類成員在子類中的訪問屬性:

父類成員訪問屬性 在父類中的含義 在子類中的含義
public 對所有類開放 對所有類開放
protected 只有包內其它類、自己和子類可以訪問 只有包內其它類、自己和子類可以訪問
缺省(default 只有包內其它類可以訪問 如果子類與父類在同一個包內,只有包內其它類可以訪問;否則相當於 private,不能訪問
private 只有自己可以訪問 不能訪問

多態

在 Java 中當把一個對象賦值給一個變量時,對象的類型必須與變量的類型相匹配。但由於 Java 有繼承的概念,便可重新定義為 一個變量可以保存其所聲明的類型或該類型的任何子類型

如果一個類型實現了接口,也可以稱之為該接口的子類型。

Java 中保存對象類型的變量是多態變量。「多態」這個術語(字面意思是許多形態)是指一個變量可以保存不同類型(即其聲明的類型或任何子類型)的對象。

多態變量:

  1. Java 的對象變量是多態的,它們能保存不止一種類型的對象。
  2. 它們可以保存的是聲明類型的對象,或聲明類型子類的對象。
  3. 當把子類的對象賦給父類的變量的時候,就發生了向上轉型。

泛型

泛型指在類定義時不設置類中的屬性或方法參數的具體類型,而是在使用(或創建對象)時再進行類型的定義。泛型本質是參數化類型,即所操作的數據類型被指定為一個參數。

泛型提供了編譯時類型安全檢測的機制,該機制允許編譯時檢測非法類型。

接口

簡介

接口(英文:Interface)在 Java 中是一個抽象類型,是抽象方法的集合,通常以 interface 來聲明。一個類通過實現接口的方式,從而來繼承接口的抽象方法。

接口並不是類,編寫接口的方式和類很相似,但是它們屬於不同的概念。類描述對象的屬性和方法。接口則包含類要實現的方法。

除非實現接口的類是抽象類,否則該類要定義接口中的所有方法。

接口無法被實例化,但是可以被實現。一個實現接口的類,必須實現接口內所描述的所有方法,否則就必須聲明為抽象類。另外,在 Java 中,接口類型可用來聲明一個變量,他們可以成為一個空指針,或是被綁定在一個以此接口實現的對象。

與類的區別

  1. 接口不能用於實例化對象。
  2. 接口沒有構造方法。
  3. 接口中所有的方法必須是抽象方法,Java 8 之後接口中可以使用 default 關鍵字修飾的非抽象方法。
  4. 接口不能包含成員變量,除了 static 和 final 變量。
  5. 接口不是被類繼承了,而是要被類實現。
  6. 接口支持多繼承,類不支持多繼承。

聲明

1
2
3
4
[可見度] interface 接口名稱 [extends 其他的接口名] {
        // 聲明變量
        // 抽象方法
}

實現

1
...implements 接口名稱[, 其他接口名稱, 其他接口名稱..., ...] ...

Lambda 表達式

簡介

lambda 表達式也可稱為閉包,是 Java 8 的最重要的新特性。

lambda 表達式允許把函數作為一個方法的參數(函數作為參數傳遞進方法中)。

使用 lambda 表達式可以使代碼變的更加簡潔緊湊。

語法

可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。

可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。

可選的大括號:如果主體包含了一個語句,就不需要使用大括號。

可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定表達式返回了一個數值。

lambda 表達式聲明方式如下:

以字符串數組按長度排序的自定義比較器為例:

  1. 參數,箭頭,一個表達式。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    import java.util.Arrays;
    
    public class Main {
        static PrintWriter out = new PrintWriter(System.out);
    
        public static void main(String[] args) {
            String[] plants = {"Mercury", "venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"};
            Arrays.sort(plants, (String first, String second) -> (first.length() - second.length()));
            for (String word : plants) {
                out.print(word + " ");
            }
            out.close();
        }
    }
    

  2. 參數,箭頭,多條語句。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import java.io.PrintWriter;
    import java.util.Arrays;
    
    public class Main {
        static PrintWriter out = new PrintWriter(System.out);
    
        public static void main(String[] args) {
            String[] plants = {"Mercury", "venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"};
            Arrays.sort(plants, (first, second) ->
            {
                // 形參不寫類型,可以從上下文判斷出
                int result = first.length() - second.length();
                return result;
            });
            for (String word : plants) {
                out.print(word + " ");
            }
            out.close();
        }
    }
    

-> 是一個推導符號,表示前面的括號接收到參數,推導後面的返回值(其實就是傳遞了方法)。

  1. 常用形式:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 1. 不需要參數,返回值為 5
() -> 5

// 2. 接收一個參數(數字類型),返回其 2 倍的值
x -> 2 * x

// 3. 接受 2 個參數(數字)並返回他們的差值
(x, y) -> x  y

// 4. 接收 2 個 int 類型整數並返回他們的和
(int x, int y) -> x + y

// 5. 接受一個 String 對象並在控制枱打印,不返回任何值(看起來像是返回 void)
(String s) -> System.out.print(s)

函數式接口

  1. 是一個接口,符合 Java 接口定義。
  2. 只包含一個抽象方法的接口。
  3. 因為只有一個未實現的方法,所以 lambda 表達式可以自動填上去。

函數式接口使用方式如下: 1. 輸出長度為 2 的倍數的字符串。

 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
import java.io.PrintWriter;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    public static void main(String[] args) {
        String[] plants = {"Mercury", "venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"};
        Test test = s -> {  // lambda 表達式作為函數式接口的實例
            if (s.length() % 2 == 0) {
                return true;
            }
            return false;
        };
        for (String word : plants) {
            if (test.check(word)) {
                out.print(word + " ");
            }
        }
        out.close();
    }
}

interface Test {
    public boolean check(String s);
}
2. 實現加減乘除四則運算。
 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
import java.io.PrintWriter;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    public static double calc(double a, double b, Calculator util) {
        return util.operation(a, b);
    }

    public static void main(String[] args) {
        Calculator util[] = new Calculator[4];  // 定義函數式接口數組
        util[0] = (a, b) -> a + b;
        util[1] = (a, b) -> a - b;
        util[2] = (a, b) -> a * b;
        util[3] = (a, b) -> a / b;
        double a = 20, b = 15;
        for (Calculator c : util) {
            System.out.println(calc(a, b, c));
        }
        out.close();
    }
}

interface Calculator {
    public double operation(double a, double b);
}

Collection

Collection 是 Java 中的接口,被多個泛型容器接口所實現。在這裏,Collection 是指代存放對象類型的數據結構。

Java 中的 Collection 元素類型定義時必須為對象,不能為基本數據類型。

以下內容用法均基於 Java 裏多態的性質,均是以實現接口的形式出現。

常用的接口包括 ListQueueSetMap

容器定義

  1. 當定義泛型容器類時,需要在定義時指定數據類型。

例如:

1
List<Integer> list1 = new LinkedList<>();
  1. 倘若不指定數據類型,而當成 Object 類型隨意添加數據,在 Java 8 中雖能編譯通過,但會有很多警告風險。

例如:

1
2
3
4
5
6
List list = new ArrayList<>();
list.add(1);
list.add(true);
list.add(1.01);
list.add(1L);
list.add("I am String");

因此,如果沒有特殊需求的話不推薦第 2 種行為,編譯器無法幫忙檢查存入的數據是否安全。list.get(index) 取值時無法明確數據的類型(取到的數據類型都為 Object),需要手動轉回原來的類型,稍有不慎可能出現誤轉型異常。

如果是明確了類型如 List<Integer>,此時編譯器會檢查放入的數據類型,只能放入整數的數據。聲明集合變量時只能使用包裝類型 List<Integer> 或者自定義的 Class,而不能是基本類型如 List<int>

List

ArrayList

ArrayList 是支持可以根據需求動態生長的數組,初始長度默認為 10。如果超出當前長度便擴容 \(\dfrac{3}{2}\)

初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();  // 創建一個名字為 list1 的可自增數組,初始長度為默認值(10)
        List<Integer> list2 = new ArrayList<>(30);  // 創建一個名字為list2的可自增數組,初始長度為 30
        List<Integer> list3 = new ArrayList<>(list2);  // 創建一個名字為 list3 的可自增數組,使用 list2 裏的元素和 size 作為自己的初始值
    }
}

LinkedList

LinkedList 是雙鏈表。

初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    public static void main(String[] args) {
        List<Integer> list1 = new LinkedList<>();  // 創建一個名字為 list1 的雙鏈表 
        List<Integer> list2 = new LinkedList<>(list1);  // 創建一個名字為 list2 的雙鏈表,將 list1 內所有元素加入進來 
    }
}

常用方法

以下均用 this 代替當前 List<Integer>

函數名 功能
size() 返回 this 的長度
add(Integer val) 在 this 尾部插入一個元素
add(int idx, Integer e) 在 this 指定位置插入一個元素
get(int idx) 返回 this 中第 idx 位置的值,若越界則拋出異常
set(int idx, Integer e) 修改 this 中第 idx 位置的值

使用案例及區別對比:

 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
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static List<Integer> array = new ArrayList<>();
    static List<Integer> linked = new LinkedList<>();

    static void add() {
        array.add(1);  // 時間複雜度為 O(1) 
        linked.add(1);  // 時間複雜度為 O(1) 
    }

    static void get() {
        array.get(10);  // 時間複雜度為 O(1) 
        linked.get(10);  // 時間複雜度為 O(11) 
    }

    static void addIdx() {
        array.add(0, 2);  // 最壞情況下時間複雜度為 O(n)
        linked.add(0, 2);  // 最壞情況下時間複雜度為 O(n)
    }

    static void size() {
        array.size();  // 時間複雜度為 O(1)
        linked.size();  // 時間複雜度為 O(1)
    }

    static void set() {  // 該方法返回值為原本該位置元素的值
        array.set(0, 1);  // 時間複雜度為 O(1)
        linked.set(0, 1);  // 最壞時間複雜度為 O(n)
    }

}

遍歷

 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
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static List<Integer> array = new ArrayList<>();
    static List<Integer> linked = new LinkedList<>();
    
    static void function1() {  // 樸素遍歷
        for (int i = 0; i < array.size(); i++) {
            out.println(array.get(i));  // 遍歷自增數組,複雜度為 O(n)
        }
        for (int i = 0; i < linked.size(); i++) {
            out.println(linked.get(i));  // 遍歷雙鏈表,複雜度為 O(n^2),因為 LinkedList 的 get(i) 複雜度是 O(i)
        }
    }

    static void function2() {  // 增強 for 循環遍歷 
        for (int e : array) {
            out.println(e);
        }
        for (int e : linked) {
            out.println(e);  // 複雜度均為 O(n) 
        }
    }

    static void function3() {  // 迭代器遍歷 
        Iterator<Integer> iterator1 = array.iterator();
        Iterator<Integer> iterator2 = linked.iterator();
        while (iterator1.hasNext()) {
            out.println(iterator1.next());
        }
        while (iterator2.hasNext()) {
            out.println(iterator2.next());
        }  // 複雜度均為 O(n) 
    }

}
注意

不要在 for/foreach 遍歷 List 的過程中刪除其中的元素,否則會拋出異常。

原因也很簡單,list.size() 改變了,但在循環中已循環的次數卻是沒有隨之變化。原來預計在下一個 index 的數據因為刪除的操作變成了當前 index 的數據,運行下一個循環時操作的會變為原來預計在下下個 index 的數據,最終會導致操作的數據不符合預期。

Queue

LinkedList

可以使用 LinkedList 實現普通隊列,底層是鏈表模擬隊列。

初始化
1
Queue<Integer> q = new LinkedList<>();

LinkedList 底層實現了 List 接口與 Deque 接口,而 Deque 接口繼承自 Queue 接口,所以 LinkedList 可以同時實現 ListQueue

ArrayDeque

可以使用 ArrayDeque 實現普通隊列,底層是數組模擬隊列。

初始化
1
Queue<Integer> q = new ArrayDeque<>();

ArrayDeque 底層實現了 Deque 接口,而 Deque 接口繼承自 Queue 接口,所以 ArrayDeque 可以實現 Queue

LinkedList 與 ArrayDeque 在實現 Queue 接口上的區別

  1. 數據結構:在數據結構上,ArrayDequeLinkedList 都實現了 Java Deque 雙端隊列接口。但 ArrayDeque 沒有實現了 Java List 列表接口,所以不具備根據索引位置操作的行為。
  2. 線程安全ArrayDequeLinkedList 都不考慮線程同步,不保證線程安全。
  3. 底層實現:在底層實現上,ArrayDeque 是基於動態數組的,而 LinkedList 是基於雙向鏈表的。
  4. 在遍歷速度上ArrayDeque 是一塊連續內存空間,基於局部性原理能夠更好地命中 CPU 緩存行,而 LinkedList 是離散的內存空間對緩存行不友好。
  5. 在操作速度上ArrayDequeLinkedList 的棧和隊列行為都是 \(O(1)\) 時間複雜度,ArrayDeque 的入棧和入隊有可能會觸發擴容,但從均攤分析上看依然是 \(O(1)\) 時間複雜度。
  6. 額外內存消耗上ArrayDeque 在數組的頭指針和尾指針外部有閒置空間,而 LinkedList 在節點上增加了前驅和後繼指針。

PriorityQueue

PriorityQueue 是優先隊列,默認是小根堆。

初始化
1
2
Queue<Integer> q1 = new PriorityQueue<>();  // 小根堆
Queue<Integer> q2 = new PriorityQueue<>((x, y) -> {return y - x;});  // 大根堆

常用方法

以下均用 this 代替當前 Queue<Integer> :

函數名 功能
size() 返回 this 的長度
add(Integer val) 入隊
offer(Integer val) 入隊
isEmpty() 判斷隊列是否為空,為空則返回 true
peek() 返回隊頭元素
poll() 返回隊頭元素並刪除

使用案例及區別對比:

 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.io.PrintWriter;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static Queue<Integer> q1 = new LinkedList<>();
    static Queue<Integer> q2 = new PriorityQueue<>();

    static void add() {  // add 和 offer 功能上沒有差距,區別是是否會拋出異常 
        q1.add(1);  // 時間複雜度為 O(1) 
        q2.add(1);  // 時間複雜度為 O(logn) 
    }

    static void isEmpty() {
        q1.isEmpty();  // 時間複雜度為 O(1) 
        q2.isEmpty();  // 空間複雜度為 O(1) 
    }

    static void size() {
        q1.size();  // 時間複雜度為 O(1) 
        q2.size();  // 返回 q2 的長度 
    }

    static void peek() {
        q1.peek();  // 時間複雜度為 O(1) 
        q2.peek();  // 時間複雜度為 O(logn) 
    }

    static void poll() {
        q1.poll();  // 時間複雜度為 O(1) 
        q2.poll();  // 時間複雜度為 O(logn) 
    }
}

遍歷

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static Queue<Integer> q1 = new LinkedList<>();
    static Queue<Integer> q2 = new PriorityQueue<>();

    static void test() {
        while (!q1.isEmpty()) {  // 複雜度為 O(n) 
            out.println(q1.poll());
        }
        while (!q2.isEmpty()) {  // 複雜度為 O(nlogn) 
            out.println(q2.poll());
        }
    }

}

Deque

DequeJava 中的雙端隊列,我們通常用其進行隊列的操作以及棧的操作。

主要函數

以下均用 this 代替當前 Deque<Integer> :

函數名 功能
push(Integer val) 將一個元素從隊頭加入this,等效於addFirst
pop() 將隊頭元素刪除,等效於removeFirst
addFirst(Integer val) 將一個元素從隊頭加入this
removeFirst() 將隊頭元素刪除,並返回該元素
addLast(Integer val) 將一個元素從隊尾加入this
removeLast() 將隊尾元素刪除,並返回該元素
offerFirst(Integer val) 將一個元素從隊頭加入this
pollFirst() 將隊頭元素刪除,並返回該元素
offerLast(Integer val) 將一個元素從隊尾加入this
pollLast() 將隊尾元素刪除,並返回該元素
add(Integer val) 將一個元素從隊尾加入this
offer(Integer val) 將一個元素從隊尾加入this
poll() 將隊頭元素刪除,並返回該元素
remove() 將隊頭元素刪除,並返回該元素
peekFirst() 返回隊頭元素
peekLast() 返回隊尾元素

addremove 操作在遇到異常時會拋出異常,而offerpoll 不會拋出異常。

棧的操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    static Deque<Integer> stack = new ArrayDeque<>();
    static int[] a = {1, 2, 3, 4, 5};

    public static void main(String[] args) {
        for (int v : a) {
            stack.push(v);
        }
        while (!stack.isEmpty()) { //輸出 5 4 3 2 1
            System.out.println(stack.pop()); 
        }
    }
}

雙端隊列的操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.ArrayDeque;
import java.util.Deque;

public class Main {
    static Deque<Integer> deque = new ArrayDeque<>();

    static void insert() {
        deque.addFirst(1);
        deque.addFirst(2);
        deque.addLast(3);
        deque.addLast(4);
    }

    public static void main(String[] args) {
        insert();
        while (!deque.isEmpty()) { //輸出 2 1 3 4
            System.out.println(deque.poll());
        }
        insert();
        while (!deque.isEmpty()) { //輸出 4 3 1 2
            System.out.println(deque.pollLast());
        }
    }
}

Set

Set 是保持容器中的元素不重複的一種數據結構。

HashSet

隨機位置插入的 Set

初始化
1
Set<Integer> s1 = new HashSet<>();

LinkedHashSet

保持插入順序的 Set

初始化
1
Set<Integer> s2 = new LinkedHashSet<>();

TreeSet

保持容器中元素有序的 Set,默認為升序。

初始化
1
2
Set<Integer> s3 = new TreeSet<>();
Set<Integer> s4 = new TreeSet<>((x, y) -> {return y - x;});  // 降序 
TreeSet的更多使用

這些方法是TreeSet新創建並實現的,我們無法使用Set接口調用以下方法,因此我們創建方式如下:

1
2
TreeSet<Integer> s3 = new TreeSet<>();
TreeSet<Integer> s4 = new TreeSet<>((x, y) -> {return y - x;});  // 降序 

以下均用 this 代替當前 TreeSet<Integer> :

函數名 功能
first() 返回 this 中第一個元素,無則返回 null
last() 返回 this 中最後一個元素,無則返回 null
floor(Integer val) 返回集合中 <=val 的第一個元素,無則返回 null
ceiling(Integer val) 返回集合中 >=val 的第一個元素,無則返回 null
higher(Integer val) 返回集合中 >val 的第一個元素,無則返回 null
lower(Integer val) 返回集合中 <val 的第一個元素,無則返回 null
pollFirst() 返回並刪除 this 中第一個元素,無則返回 null
pollLast() 返回並刪除 this 中最後一個元素,無則返回 null

代碼示例:

 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
import java.util.TreeSet;

public class Main {
    static int[] a = {4,7,1,2,3,6};

    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        for(int v:a) {
            set.add(v);
        }
        Integer a2 = set.first();
        System.out.println(a2); //返回 1
        Integer a3 = set.last();
        System.out.println(a3); //返回 7
        Integer a4 = set.floor(5);
        System.out.println(a4); //返回 4
        Integer a5 = set.ceiling(6);
        System.out.println(a5); //返回 6
        Integer a6 = set.higher(7);
        System.out.println(a6); //返回 null
        Integer a7 = set.lower(2);
        System.out.println(a7); //返回 1
        Integer a8 = set.pollFirst();
        System.out.println(a8); //返回 1
        Integer a9 = set.pollLast();
        System.out.println(a9); //返回 7
    }
}

Set常用方法

以下均用 this 代替當前 Set<Integer> :

函數名 功能
size() 返回 this 的長度
add(Integer val) 插入一個元素進 this
contains(Integer val) 判斷 this 中是否有元素 val
addAll(Collection e) 將一個容器裏的所有元素添加進 this
retainAll(Collection e) 將 this 改為兩個容器內相同的元素
removeAll(Collection e) 將 this 中與 e 相同的元素刪除

使用案例:求並集、交集、差集。

 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
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static Set<Integer> s1 = new HashSet<>();
    static Set<Integer> s2 = new LinkedHashSet<>();

    static void add() {
        s1.add(1);
    }

    static void contains() {  // 判斷 set 中是否有元素值為 2,有則返回 true,否則返回 false 
        s1.contains(2);
    }

    static void test1() {  // s1 與 s2 的並集 
        Set<Integer> res = new HashSet<>();
        res.addAll(s1);
        res.addAll(s2);
    }

    static void test2() {  // s1 與 s2 的交集 
        Set<Integer> res = new HashSet<>();
        res.addAll(s1);
        res.retainAll(s2);
    }

    static void test3() {  // 差集:s1 - s2 
        Set<Integer> res = new HashSet<>();
        res.addAll(s1);
        res.removeAll(s2);
    }
}

遍歷

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);
    static Set<Integer> s1 = new HashSet<>();
    static Set<Integer> s2 = new LinkedHashSet<>();

    static void test() {
        for (int key : s1) {
            out.println(key);
        }
        out.close();
    }
}

Map

Map 是維護鍵值對 <Key, Value> 的一種數據結構,其中 Key 唯一。

HashMap

隨機位置插入的 Map

初始化
1
Map<Integer, Integer> map1 = new HashMap<>();

LinkedHashMap

保持插入順序的 Map

初始化
1
Map<Integer, Integer> map2 = new LinkedHashMap<>();

TreeMap

保持 key 有序的 Map,默認升序。

初始化
1
2
Map<Integer, Integer> map3 = new TreeMap<>();
Map<Integer, Integer> map4 = new TreeMap<>((x, y) -> {return y - x;});  // 降序

常用方法

以下均用 this 代替當前 Map<Integer, Integer>

函數名 功能
put(Integer key, Integer value) 插入一個元素進 this
size() 返回 this 的長度
containsKey(Integer val) 判斷 this 中是否有元素 key 為 val
get(Integer key) 將 this 中對應的 key 的 value 返回
keySet 將 this 中所有元素的 key 作為集合返回

使用案例:

 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.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    static Map<Integer, Integer> map1 = new HashMap<>();
    static Map<Integer, Integer> map2 = new LinkedHashMap<>();
    static Map<Integer, Integer> map3 = new TreeMap<>();
    static Map<Integer, Integer> map4 = new TreeMap<>((x,y)->{return y-x;});

    static void put(){  // 將 key 為 1、value 為 1 的元素返回
        map1.put(1, 1);
    }
    static void get(){  // 將 key 為 1 的 value 返回
        map1.get(1);
    }
    static void containsKey(){  // 判斷是否有 key 為 1 的鍵值對
        map1.containsKey(1);
    }
    static void KeySet(){
        map1.keySet();
    }
}

遍歷

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    static Map<Integer, Integer> map1 = new HashMap<>();

    static void print() {
        for (int key : map1.keySet()) {
            out.println(key + " " + map1.get(key));
        }
    }
}

當然,在面向對象的世界裏,你的參數是什麼都可以,包括 Collection 與自定義類型。

例如 Map 也可以定義為:

1
Map<String, Set<Integer>> map = new HashMap<>();

Arrays

Arraysjava.util 中對數組操作的一個工具類。方法均為靜態方法,可使用類名直接調用。

Arrays.sort()

Arrays.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
import java.util.Arrays;
import java.util.Comparator;

public class Main {
    static int[] a = new int[10];
    static Integer[] b = new Integer[10];
    static int firstIdx, lastIdx;

    public static void main(String[] args) {
        Arrays.sort(a);  // 1 
        Arrays.sort(a, firstIdx, lastIdx);  // 2 
        Arrays.sort(b, new Comparator<Integer>() {  // 3 
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        Arrays.sort(b, firstIdx, lastIdx, new Comparator<Integer>() {  // 4 
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        // 由於 Java 8 後有 Lambda 表達式,第三個重載及第四個重載亦可寫為 
        Arrays.sort(b, (x, y) -> {  // 5 
            return y - x;
        });
        Arrays.sort(b, (x, y) -> {  // 6 
            return y - x;
        });
    }
}

序號所對應的重載方法含義:

  1. 對數組 a 進行排序,默認升序。
  2. 對數組 a 的指定位置進行排序,默認升序,排序區間為左閉右開 [firstIdx, lastIdx)
  3. 對數組 a 以自定義的形式排序,第二個參數 - 第一個參數為降序,第一個參數 - 第二個參數為升序,當自定義排序比較器時,數組元素類型必須為對象類型。
  4. 對數組 a 的指定位置進行自定義排序,排序區間為左閉右開 [firstIdx, lastIdx),當自定義排序比較器時,數組元素類型必須為對象類型。
  5. 和 3 同理,用 Lambda 表達式優化了代碼長度。
  6. 和 4 同理,用 Lambda 表達式優化了代碼長度。

Arrays.sort() 底層函數:

  1. 當你 Arrays.sort 的參數數組元素類型為基本數據類型(byteshortcharintlongdoublefloat)時,默認為 DualPivotQuicksort(雙軸快排),複雜度最壞可以達到 \(O(n^2)\)
  2. 當你 Arrays.sort 的參數數組元素類型為非基本數據類型時,則默認為 legacyMergeSortTimSort(歸併排序),複雜度為\(O(n\log n)\)

可以通過如下代碼驗證:

Codeforces 1646B - Quality vs Quantity

題意概要:有 \(n\) 個數,你需要將其分為 2 組,是否能存在 1 組的長度小於另 1 組的同時和大於它。

例題代碼
 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    static class FastReader {
        StringTokenizer st;
        BufferedReader br;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }

    static PrintWriter out = new PrintWriter(System.out);
    static FastReader in = new FastReader();

    static void solve() {
        int n = in.nextInt();
        Integer[] a = new Integer[n + 10];
        for (int i = 1; i <= n; i++) {
            a[i] = in.nextInt();
        }
        Arrays.sort(a, 1, n + 1);
        long left = a[1];
        long right = 0;
        int x = n;
        for (int i = 2; i < x; i++, x--) {
            left = left + a[i];
            right = right + a[x];
            if (right > left) {
                out.println("YES");
                return;
            }
        }
        out.println("NO");
    }

    public static void main(String[] args) {
        int t = in.nextInt();
        while (t-- > 0) {
            solve();
        }
        out.close();
    }
}

如果你將以上代碼的 a 數組類型由 Integer 修改為 int,會導致 TLE。

Arrays.binarySearch()

Arrays.binarySearch() 是對數組連續區間進行二分搜索的方法,前提是數組必須有序,時間複雜度為 \(O(\log_n)\),主要重載方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import java.util.Arrays;

public class Main {
    static int[] a = new int[10];
    static Integer[] b = new Integer[10];
    static int firstIdx, lastIdx;
    static int key;

    public static void main(String[] args) {
        Arrays.binarySearch(a, key);  // 1 
        Arrays.binarySearch(a, firstIdx, lastIdx, key);  // 2 
    }
}

源碼如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }

序號所對應的重載方法含義:

  1. 從數組 a 中二分查找是否存在 key,如果存在,便返回其下標。若不存在,則返回一個負數。
  2. 從數組 a 中二分查找是否存在 key,如果存在,便返回其下標,搜索區間為左閉右開 [firstIdx,lastIdx)。若不存在,則返回一個負數。

Arrays.fill()

Arrays.fill() 方法將數組中連續位置的元素賦值為統一元素。其接受的參數為數組、fromIndextoIndex 和需要填充的數。方法執行後,數組左閉右開區間 [firstIdx,lastIdx) 內的所有元素的值均為需要填充的數。

Collections

Collectionsjava.util 中對集合操作的一個工具類。方法均為靜態方法,可使用類名直接調用。

Collections.sort()

Collections.sort() 底層原理為將其中所有元素轉化為數組調用 Arrays.sort(),完成排序後再賦值給原本的集合。又因為 Java 中 Collection 的元素類型均為對象類型,所以始終是歸併排序去處理。

該方法無法對集合指定區間排序。

底層源碼:

1
2
3
4
5
6
7
8
9
  default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

Collections.binarySearch()

Collections.binarySearch() 是對集合中指定區間進行二分搜索,功能與 Arrays.binarySearch() 相同。

1
Collections.binarySearch(list, key);

該方法無法對指定區間進行搜索。

Collections.swap()

Collections.swap() 的功能是交換集合中指定二個位置的元素。

1
 Collections.swap(list, i, j);

其他

1. -0.0 != 0.0

在 Java 中,如果單純是數值類型,-0.0 = 0.0 。若是對象類型,則 -0.0 != 0.0 。倘若你嘗試用 Set 統計斜率數量時,這個問題就會帶來麻煩。 提供的解決方式是在所有的斜率加入 Set 前將值增加 0.0。

 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
import java.io.PrintWriter;

public class Main {
    static PrintWriter out = new PrintWriter(System.out);

    static void A() {
        Double a = 0.0;
        Double b = -0.0;
        out.println(a.equals(b));  // false 
    }

    static void B() {
        Double a = 0.0;
        Double b = -0.0 + 0.0;
        out.println(a.equals(b));  // true 
    }

    static void C() {
        double a = 0.0;
        double b = -0.0;
        out.println(a == b);  // true 
    }


    public static void main(String[] args) {
        A();
        B();
        C();
        out.close();
    }
}

參考資料