程式碼隨想錄
滑動視窗
1、如果給兩個字串s和t,判斷t是否為s的子串或是否s包含t的排列,用t的長度固定滑動視窗的大小,初始化將s的前t.length()個長度的字元情況儲存在int陣列中,int陣列的大小由字串中字元的型別決定,最大為ascii表的長度,為128。
每次迴圈滑動視窗向前移一位,即left++,right++,移完之後和儲存t字串情況的陣列進行比較,Arrays.equal(int[] a,int[] b)。
例:567. 字串的排列 - 力扣(LeetCode )
438. 找到字串中所有字母異位詞 - 力扣(LeetCode)
2、對於找不重複的最長子串,可以用int[]陣列是否大於1來判斷,大於1時則right指向的字元子串中已經包含,此時先將left指向的字元移出,然後再left++(而不是直接移到right的位置×!!)。
例:3. 無重複字元的最長子串 - 力扣(LeetCode)
具體程式碼
public int lengthOfLongestSubstring(String s) {
int left=0;
int right=0;
int count=0;
int[] curr=new int[128];
while (right<s.length()){
if (curr[s.charAt(right)]<1){
curr[s.charAt(right)]++;
right++;
} else if (curr[s.charAt(right)]>=1) {
curr[s.charAt(left)]--;
left++;
}
count= count>right-left?count:right-left;
}
return count;
}
螺旋矩陣
當輸入的矩陣並不一定行數列數相等時,要考慮到四種不同的情況,分別是:
-
- 行數為1行或者列數為1列 直接從左往右 或者從上往下遍歷
- 行數(列數)為2的倍數且行數≤列數(列數≤行數) 遍歷完整個迴圈之後 就可以結束 不需要後續操作
- 行數=列數,且為奇數時 只需要在迴圈結束後再遍歷一下當前最中心的值
- 行數(列數)為奇數且行數<列數(列數<行數) 在迴圈結束之後再繼續遍歷(列數-行數+1)箇中心列元素或者是再繼續遍歷(行數-列數+1)箇中心行元素
螺旋矩陣程式碼
public List<Integer> spiralOrder(int[][] matrix) {
int row=matrix.length;//行數
int column=matrix[0].length;//列數
ArrayList<Integer> list = new ArrayList<>();
int x=0;//矩陣的行位置
int y=0;//矩陣列位置
int loop=1;//迴圈的次數
int offset=1;//遍歷的長度 每次迴圈一遍右邊界減一
int i=0,j=0;
//針對一行或者一列的情況
if (column==1 ){
while (x<row)
{
list.add(matrix[x++][0]);
}
}else if (row==1){
while (y<column){
list.add(matrix[0][y++]);
}
}
//迴圈主體
while (loop<=Math.min(row/2,column/2)){
for (j=y;j<column-offset;j++){
list.add(matrix[x][j]);
}
for (i = x;i < row-offset; i++) {
list.add(matrix[i][j]);
}
for (;j>y;j--){//注意:j是大於y而不是大於0
list.add(matrix[i][j]);
}
for (;i>x;i--){//注意:i是大於x而不是大於0
list.add(matrix[i][j]);
}
loop++;//迴圈次數++
x++;//行位置往裡移動一位
y++;//列位置往下移動一位
offset++;//行和列中不需要遍歷的個數+1
}
//如果存在列的個數是2的倍數且行數>=列數時,迴圈完正好結束,沒有裡層剩餘,列和行反過來一樣
if ((column%2==0 && column<row) ||(row%2==0 && column>row)){
return list;
}
//當裡層有剩餘時
i=x;
j=y;
if ( i<column && j<row){ //因為上面while大迴圈中最後x++,y++ 所以需要進行一下判斷
if (row==column && row%2==1){//如果行數和列數相等且為奇數,那麼只剩最中間一個沒有遍歷
list.add(matrix[i][j]);
} else if (row>column) {//如果行數>列數 且列數是奇數 那麼中間有有一小列元素沒有遍歷
while (i-j <=row-column){//沒有遍歷的個數是行數-列數+1
list.add(matrix[i++][j]);
}
}else if (row<column){//行數列數反過來 同上
while (j-i <=column-row){
list.add(matrix[i][j++]);
}
}
}
return list;
}
區間和
直接每次輸入區間後,都需要遍歷一次區間中的資料,因此時間消耗為0(m*n) m表示輸入區間的次數,n為陣列的元素個數
解決方法:使用字首和,將前i個元素之和儲存在一個陣列中,每次輸入區間之後,直接用末位置的和➖(初位置-1)的和,當初位置為0時區間和就為末位置之和。
區間和程式碼
import java.util.*;
public class Main{
public static void main (String[] args) {
Scanner scan= new Scanner(System.in);
int arrLength=scan.nextInt();
int[] Array=new int[arrLength];
int[] p=new int[arrLength];
int i=0;
Array[i]=scan.nextInt();
p[i]=Array[0];
for(i=1;i<arrLength;i++){
Array[i]=scan.nextInt();
p[i]=p[i-1]+Array[i];
}
while(scan.hasNextInt()){
int a=scan.nextInt();
int b=scan.nextInt();
if (a==0){
System.out.println(p[b]);
}else{
System.out.println(p[b]-p[a-1]);
}
}
scan.close();
}
}
開發商購買土地
其核心在於,劃分要麼橫著一刀,要麼豎著一刀,且只能是一刀。
思路:先驗證橫著一刀得到兩個區域差的最小值:遍歷每一行,在每行最後一個位置加入後計算區域差,sum-count-count,其中(sum-count)表示此一刀下面的區域,count表示一刀上面的區域,用abs(下面的區域和➖上面區域和)則表示區域差;同理再試豎著的一刀。
字首和和最佳化暴力求解都是o(m*n)
暴力求解程式碼
public class Main{
public static void main (String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
int m=scanner.nextInt();
int[][] vec=new int[n][m];
int sum=0;
int result=Integer.MAX_VALUE;
for (int i=0;i<n ;i++ ){
for (int j=0;j<m ;j++ ){
vec[i][j]=scanner.nextInt();
sum+=vec[i][j];
}
}
int count=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
count+=vec[i][j];
if (j==m-1){
result=Math.min(result,Math.abs(sum-count-count));
}
}
}
count=0;
for(int j=0;j<m;j++){
for(int i=0;i<n;i++){
count+=vec[i][j];
if(i==n-1){
result=Math.min(result,Math.abs(sum-count-count));
}
}
}
System.out.println(result);
scanner.close();
}
}
陣列基礎總結
陣列的元素是不能刪除的,只能覆蓋!!
二維陣列在記憶體中並不是連續的,其實質是一維陣列的一維陣列,即物件中儲存著一個一維陣列的地址,這個一維陣列中儲存著每行陣列的起始地址。
Java基礎學習
資料結構
1、在底層真實存在的資料結構:陣列、連結串列
抽象資料型別:樹、棧、佇列(使用陣列或者是連結串列來構建)
集合原始碼
ArrayList
1、ArrayList的特點
- 實現了List介面,儲存有序的,可重複的一個一個的資料
- 底層使用object[]陣列儲存
- 執行緒不安全
2、ArrayList原始碼解析
jdk7版本:
//底層會初始化陣列,陣列的長度為10,object[] elementDate=new object[10];
ArrayList arr=new ArrayList<>();
arr.add("AA");//elementDate[0]="AA";
arr.add("BB");//elementDate[1]="BB";
//一旦size>length,length變為原來的1.5倍,並將原ArrayList複製到擴容的ArrayList
jdk8版本:
//底層不會初始化陣列,陣列的長度為0,object[] elementDate=new Object[]{};
ArrayList arr=new ArrayList<>();
arr.add("AA");//首次新增元素時,會初始化陣列elementData=new Object[10];elementData[0]="AA";
arr.add("BB");//elementDate[1]="BB";
//一旦size>length,length變為原來的1.5倍,並將原ArrayList複製到擴容的ArrayList
3、在選擇ArrayList時,有兩種初始化方法: new ArrayList();//底層建立長度為10的陣列
new ArrayList(int capacity); //底層建立指定capacity長度的陣列
Vector
1、Vector的特點
- 實現了List介面,儲存有序的,可重複的資料
- 底層使用Object[]陣列儲存
- 執行緒安全
2、Vector原始碼解析
Vector v=new Vector();//底層初始化陣列,長度為10 Object[] elementData=new Object[10]
v.add("AA");//elementData[0]="AA";
v.add("BB");//elementData[1]="BB";
//當新增第11個元素時,需要擴容,預設擴容為原來的2倍
LinkedList
1、LinkedList的特點
- 實現了List介面,儲存有序的,可重複的資料
- 底層使用雙向連結串列儲存
- 執行緒不安全
2、LinkedList原始碼解析
LinkedList<String> list=new LinkedList<>();//因為底層不是陣列結構,不需要初始化分配陣列空間
list.add("AA");//將AA封裝到一個Node物件1中,list物件的屬性first、last都指向此Node物件1
list.add("BB");//將BB封裝到一個Node物件2中,與物件1構成一個雙向連結串列,此時last指向Node2物件
//由於LinkedList使用的是雙向連結串列,不考慮擴容的問題
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
HashMap⭐
1、HashMap特點
- 實現Map介面,儲存一對一對的資料,可以新增null的key值和value值
- 底層使用陣列+單向連結串列+紅黑樹(jdk8版本之後),jdk7版本沒有使用紅黑樹
- 執行緒不安全,效率高的
2、HashMap原始碼解析
- JDK7
//建立物件的過程中,底層初始化陣列Entry[] table=new Entry[16]
HashMap<String,Integer> map=new HashMap<>();
map.put("AA",78);//將AA和78封裝到一個Entry物件中,考慮將此物件新增到table陣列中
情況1:將(key1,value1)存放到陣列的索引i的位置
情況2,情況3:使用單向連結串列的頭插法,將(key1,value1)放在陣列中,並指向(key2,value2)
滿足下列條件,會考慮擴容,一般擴容為table.legth的2倍
(size>=threshold) && (null !=table[i]) threshold=陣列的長度*載入因子(預設為0.75),
載入因子過大,增加元素的數目較大時才會擴容,節省擴容所需的時間,但後期查詢和刪除比較麻煩
載入因子過小,沒新增幾個元素就開始擴容,浪費空間
HashMap有無參構造器,也有有參構造器,其中無參構造器預設capacity為16,LOAD_FACTOR為0.75,也可以直接呼叫有參構造器,手動輸入上面兩個引數的值,其中就算手動輸入的capacity不是2的倍數,經過構造器之後,也會構建大小為2的倍數的capacity
HashMap允許新增key為null的值,將此(key,value)存放到table索引0的位置,如果索引為0的位置已經有元素了且遍歷該位置的連結串列不為null,則將null用頭插法放入HashMap陣列中,若索引為0的key也為null或連結串列中有key為null,則將新的value賦給舊的value值
詳細可看:JDK7中HashMap的原始碼 影片171集33:49
- JDK8(以jdk1.8.0_271為例)
① 在jdk8中,當我們建立了HashMap例項以後,底層並沒有初始化table陣列。當首次新增(key,value)時,進行判斷,如果發現table尚未初始化,則對陣列進行初始化。
② 在jdk8中,HashMap底層定義了Node內部類,替換jdk7中的Entry內部類。意味著,我們建立的陣列是Node[]
③ 在jdk8中,如果當前的(key,value)經過一系列判斷之後,可以新增到當前的陣列角標i中。如果此時角標i位置上有元素。在jdk7中是將新的(key,value)指向已有的舊的元素(頭插法),而在jdk8中是舊的元素指向新的 (key,value)元素(尾插法)。 "七上八下"
④ jdk7:陣列+單向連結串列 ;jk8:陣列+單向連結串列 + 紅黑樹
3、單連結串列和紅黑樹轉換時機
- 使用單向連結串列變為紅黑樹:如果陣列索引i位置上的元素的個數達到8,並且陣列的長度達到64時,我們就將此索引i位置上的多個元素改為使用紅黑樹的結構進行儲存。(為什麼修改呢?因為紅黑樹進行put()/get()/remove()操作的時間複雜度為O(logn),比單向連結串列的時間複雜度O(n)的好,效能更高。)
- 使用紅黑樹變為單向連結串列:當使用紅黑樹的索引i位置上的元素的個數低於6的時候,就會將紅黑樹結構退化為單向連結串列。
屬性/欄位:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 預設的初始容量 16
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 1 << 30
static final float DEFAULT_LOAD_FACTOR = 0.75f; //預設載入因子
static final int TREEIFY_THRESHOLD = 8; //預設樹化閾值8,當連結串列的長度達到這個值後,要考慮樹化
static final int UNTREEIFY_THRESHOLD = 6;//預設反樹化閾值6,當樹中結點的個數達到此閾值後,要考慮變為連結串列
//當單個的連結串列的結點個數達到8,並且table的長度達到64,才會樹化。
//當單個的連結串列的結點個數達到8,但是table的長度未達到64,會先擴容(比JDK7多的一種擴容情況)
static final int MIN_TREEIFY_CAPACITY = 64; //最小樹化容量64
transient Node<K,V>[] table; //陣列
transient int size; //記錄有效對映關係的對數,也是Entry物件的個數
int threshold; //閾值,當size達到閾值時,考慮擴容
final float loadFactor; //載入因子,影響擴容的頻率
LinkedHashMap
1、LinkedHashMap與HashMap的關係
LinkedHashMap是HashMap的子類
LinkedHashMap在HashMap陣列+單向連結串列+紅黑樹的基礎上,又增加了一對雙向連結串列(Entry類繼承了HashMap的Node,並新增before和after作為雙向指標),記錄新增的(key,value)先後順序
2、LinkedHashMap的put()方法使用了HashMap的put方法,但重寫了put方法中的NewNode()方法;
HashSet和LinkedHashSet
HashSet和LinkedHashSet的底層分別是HashMap和LinkedHashMap,新增元素相當於新增key,其中key都指向同一個value,為new Object()
當對HashSet中已經存在的元素進行修改,並使用remove()方法對最新資料進行移除時,並不會刪除掉該元素,因為hash值發生改變,現set中儲存的卻是原hash值。
新增時,不論是新的元素還是舊元素都可以新增成功。
面試⭐:
1、ArrayList相當於對陣列的常見操作的封裝,對外暴露增刪改查插的操作方法
2、HashMap初始值為16(檢視原始碼),臨界值是12(threshold ,使用陣列的長度*載入因子)
3、HashMap為什麼是2的次方?看底層程式碼,方便計算在陣列中存放角標的位置,需要和陣列的length-1進行與運算,length必須為2的n次方
4、HashMap計算索引值,在1.7使用Indexfor()方法,在1.8中進行&運算
5、雜湊衝突:
解決方法:頭插法、尾插法、紅黑樹、比較equals
6、HashMap退化是因為紅黑樹佔用空間大,TreeNode要佔據兩倍的普通Node的空間
7、hashCode()與equals()生成演算法、方法怎麼重寫?進行equals()判斷使用的屬性,通常也都會參與到hashCode()的計算中
IDEA自動生成hashCode()自動使用相關演算法
File類和IO流
1、流的基礎操作
輸入流 輸出流
位元組流: InputStream OutputStream
字元流: Reader Writer
2、字元和位元組的關係 字元流和位元組流是兩個單位
一個char是兩個byte 一個byte佔8bit=>一個字元佔兩個位元組 一個位元組8位元(二進位制數)
3、new FileReader(char cbuffer);
該方法會返回每次讀到的字元個數 且讀到的字元會存在cbuffer中,迴圈輸出時,必須要小於返回的字元個數len,因為後面迴圈讀到的字元會覆蓋前面迴圈讀到的字元,一但後續長度不夠上一輪結果有部分會儲存下來。
4、try-catch
執行完後還會繼續往下執行,使用finally是避免catch丟擲異常,後面的語句沒有執行。
當try中由語句丟擲異常,該語句後面的部分將不會繼續執行。
節點流(FileReader FileWriter FileInputStream FileOutputStream)
1、讀寫字元流資料都有一定的步驟,且關閉資料流必須要放在try-catch-finally的finally
中,確保即使執行過程中出錯,也會關閉資料流,而當資料輸入輸出資料流為空時,則不需要進行關閉。
一定要關閉流資源,為了避免記憶體洩漏(資料已經使用完,但gc並不會回收)
public void test4(){
//1.建立File類的物件
File srcFile=new File("hello.txt");
File destFile=new File("hello_copy.txt");
//2.建立輸入輸出流
FileReader fr = null;
FileWriter fw= null;
try {
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.資料的讀入和寫出的過程
char[] cbuffer=new char[5];
int len;
while ((len=fr.read(cbuffer))!=-1){
fw.write(cbuffer,0,len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//4.關閉流資源
try {
if (fw!=null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr!=null)
fr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2、除了文字檔案以外,其它型別的檔案使用位元組流,和字元流的基礎操作一樣,除了方法裡面的引數變成byte型別,如:read(byte[] buffer)
和write(byte[] buffer,0,len)
3、IDEA中預設UTF-8儲存,漢字預設為3個位元組,其它為1個位元組,當使用位元組流讀取複製到另一個檔案時,複製成功,而如果是讀出來是到控制檯,如果正好中間斷開中文,則會報亂碼。
4、對於字元流,只能用來處理文字檔案,不能用來處理非文字檔案。
對於位元組流,通常是用來處理非文字檔案,但是如果涉及到文字檔案的複製可以使用位元組流
文字檔案:.txt .java .c .py等各種程式語言檔案
非文字檔案:.doc等支援圖片的
處理流
緩衝流
1、使用緩衝流 BufferInputStream BufferOutPutStream
提高檔案讀寫的效率 預設緩衝區大小8*1024 即8kb
緩衝流的操作是先統一寫到記憶體緩衝區裡,在關閉資源時再統一寫入到檔案,以此提高效率,所以檔案的最後如果沒有關閉,則會缺失資料,此時可以用.flush()
方法做到每用緩衝流寫一次都會重新整理資料
節點流不關閉也不會缺失資料,因為每呼叫一次wirite()方法都寫入到檔案.
轉換流
1、位元組流-》字元流 解碼 InputStreamReader 字元流-》位元組流 編碼 OutputStreamWrite
2、解碼時使用的字符集必須與當初編碼使用的字符集相容;如果檔案編碼使用的GBK,解碼是UTF-8,但檔案中只有abc等英文字元,此情況不會出現亂碼,因為GBK和UTF-都向下相容了ASCII。ASCII包括英文字母,數字和一些符號總共是128個,佔7位,用一個位元組表示
FileInputStream fis=new FileInputStream(file1);
FileOutputStream fos=new FileOutputStream(file2);
//對應的解碼,必須與原字符集相同
InputStreamReader isr=new InputStreamReader(fis,"gbk");
OutputStreamWriter osw=new OutputStreamWriter(fos,"utf8");
byte[] buffer=new byte[5];
int len;
while ((len=bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
3、在儲存的檔案中的字元
資料流和物件流
1、物件的序列化機制
序列化過程:寫出到磁碟或透過網路傳輸出去的過程,使用ObjectOutputStream流實現。
反序列化過程:將檔案中的資料或網路傳輸過來的資料還原為記憶體中的Java物件,使用ObjectInputStream流實現。
2、流程
- 建立File物件
- File物件作為引數,建立節點流
- 節點流物件作為引數,建立物件流,呼叫物件流的readObject()和writeObject()方法完成對物件流的輸入和輸出
- 關閉最外層物件流
//資料流寫到磁碟
@Test
public void test1() throws IOException {
File file=new File("object.dat");
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
Person p=new Person("enheng",12);
oos.writeObject(p);
oos.flush();
oos.close();
}
//從磁碟寫到記憶體
@Test
public void test2() throws IOException, ClassNotFoundException {
File file=new File("object.dat");
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
Person p=(Person) ois.readObject();
System.out.println(p);
ois.close();
}
3、自定義類要實現序列化機制,要滿足
類中的屬性如果宣告為transient或static,則不會儲存在磁碟檔案上,輸出結果為預設值null或0
程序與程序之間通訊,客戶端與客戶端之間進行通訊,都需要物件是可序列化的。
網路程式設計
InetAddress
InetAddress類沒有明顯的建構函式,上面兩個方法為工廠方法,即一個類中的靜態方法返回該類的一個例項。
InetAddress inet1= InetAddress.getByName("192.168.23.21");
System.out.println(inet1);
InetAddress inet2=InetAddress.getByName("www.atguigu.com");
System.out.println(inet2);
TCP和UDP
1、TCP的三次握手
2、四次揮手
3、Socket是Ip地址+埠號
檔案透過TCP從客戶端傳到伺服器
package com.atguigu02.tcpudp;
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* className:TCPTEst
* Description:
*
* @Author 董雅潔
* @Create 2024/11/22 15:07
* @Version 1.0
*/
public class TCPTest {
@Test
//客戶端 傳送檔案給伺服器端
public void client() throws IOException {
Socket socket = null;
FileInputStream fis= null;//從磁碟讀到記憶體 用InputStram
OutputStream os = null;
try {
//1.建立Socket
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port=9090;
socket = new Socket(inetAddress, port);
//2.建立File例項、FileInputStream的例項
File file=new File("1.png");
fis = new FileInputStream(file);
//3.透過Socket,獲取輸出流
os = socket.getOutputStream();
//4.讀寫資料
byte[] buffer=new byte[1024];
int len;
while ((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
System.out.println("資料傳送完畢");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os!=null){
os.close();
}
if (fis!=null){
fis.close();
}
if (socket!=null){
socket.close();
}
}
}
//伺服器端 接受客戶端發來的檔案
@Test
public void server() throws IOException {
//1.建立ServerSocket
int port=9090;
ServerSocket serverSocket = new ServerSocket(port);
//2.呼叫accept() 接收客戶端的Socket
Socket socket = serverSocket.accept();
System.out.println("伺服器已開啟");
//3.透過Socket獲取一個輸入流
InputStream is = socket.getInputStream();
//4.建立File類的例項 FileOutputStream的例項
File file=new File("1_copy.png");
FileOutputStream fos=new FileOutputStream(file);
byte[] buffer=new byte[1024];//當內容為中文時,使用一般方式可能會亂碼
//ByteArrayOutputStream bos=new ByteArrayOutputStream();
int len;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}