Java數據結構和算法day01 稀疏數組與隊列

第一章 數據結構和算法概述

幾個經典的算法面試題

  • 例題1——字符串匹配問題
字符串匹配問題:

1) 有一個字符串 str1= "結構與算法 數據和結構 數據結構與算法數據",和一個子串 str2="數據結構與算法"

2) 現在要判斷str1是否含有str2, 如果存在,就返回第一次出現的位置, 如果沒有,則返回-1

3)要求用最快的速度來完成匹配

4)你的思路是什麼?


思路:
	• 暴力匹配(一個字符一個字符一一對應匹配)

	• KMP算法《部分匹配表》(先知道有這個東西即可!!!)
  • 例題2——漢諾塔遊戲
漢諾塔遊戲, 請完成漢諾塔遊戲的代碼: 要求:
    1) 將A塔的所有圓盤移動到C塔。並且規定,在2) 小圓盤上不能放大圓盤,3)在三根柱子之間一次只能移動一個圓盤
    
操作步驟:
三個柱子!分別爲123號
五個盤子 A B C D E
這樣走:
A-3 B-2 A-2
C-3 A-1 B-3 A-3
D-2 A-2 B-1 A-1 C-2 A-3 B-2 1-2
E-3 A-1 B-3 A-3 C-1 A-2 B-1 A-1 D-3 A-3 B-2 A-2 C-3
A-1 B-3 A-3 得出
    
用編程的方式:使用到分治算法.     

在這裏插入圖片描述

八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即:任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。【92】

用編程的方式:使用到分治算法或回溯算法

高斯認爲有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。計算機發明後,有多種計算機語言可以解決此問題    

在這裏插入圖片描述

  • 例4——馬踏棋盤算法
馬踏棋盤算法介紹和遊戲演示
1) 馬踏棋盤算法也被稱爲騎士周遊問題
2) 將馬隨機放在國際象棋的8×8棋盤Board[07][07]的某個方格中,馬按走棋規則(馬走日字)進行移動。要求每個方格只進入一次,走遍棋盤上全部64個方格
3) 遊戲演示: http://www.4399.com/flash/146267_2.htm 

4) 用編程的方式:會使用到圖的深度優化遍歷算法(DFS) + 貪心算法優化

在這裏插入圖片描述

數據結構和算法的重要性

  1. 算法是程序的靈魂,優秀的程序可以在海量數據計算時,依然保持高速計算。

  2. 一般來講 程序會使用了內存計算框架(比如Spark)和緩存技術(比如Redis等)來優化程序,再深入的思考一下,這些計算框架和緩存技術, 它的核心功能是哪個部分呢?

  3. 拿實際工作經歷來說, 在Unix下開發服務器程序,功能是要支持上千萬人同時在線, 在上線前,做內測,一切OK,可上線後,服務器就支撐不住了, 公司的CTO對代碼進行優化,再次上線,堅如磐石。你就能感受到程序是有靈魂的,就是算法。

  4. 目前程序員面試的門檻越來越高,很多一線IT公司(大廠),都會有數據結構和算法面試題(負責的告訴你,肯定有的)。

  5. 如果你不想永遠都是代碼工人,那就花時間來研究下數據結構和算法。

數據結構與算法框架

這個只是大體框架圖,後續會逐漸修正,不足之處多多斧正!!!

在這裏插入圖片描述

數據結構和算法的關係(瞭解!!!)

  • 數據data結構(structure)是一門研究組織數據方式的學科,有了編程語言也就有了數據結構.學好數據結構可以編寫出更加漂亮,更加有效率的代碼。
  • 要學習好數據結構就要多多考慮如何將生活中遇到的問題,用程序去實現解決。
  • 程序 = 數據結構 + 算法
  • 數據結構是算法的基礎, 換言之,想要學好算法,需要把數據結構學到位。

編程中實際遇到的幾個問題(瞭解)

  • 問題一:字符串替換問題
public static void main(String[] args) {
		String str = "Java,Java, hello,world!";
		String newStr = str.replaceAll("Java", "數據結構"); //算法
		System.out.println("newStr=" + newStr);
}

問:試寫出用單鏈表表示的字符串類及字符串結點類的定義,並依次實現它的構造函數、以及計算串長度、串賦值、判斷兩串相等、求子串、兩串連接、求子串在串中位置等7個成員函數。

小結:需要使用到單鏈表數據結構

  • 問題二:一個五子棋程序

在這裏插入圖片描述

如何判斷遊戲的輸贏,並可以完成存盤退出和繼續上局的功能
思路:    
    1)將棋盤構建二維數組=>(稀疏數組)->寫入文件【存檔功能】
    2)讀取文件-》稀疏數組-》二維數組-》棋盤【接上局】
  • 問題三:約瑟夫(Josephu)問題(丟手帕問題)

在這裏插入圖片描述

	1) Josephu  問題爲:設編號爲12,… n的n個人圍坐一圈,約定編號爲k(1<=k<=n)的人從1開始報數,數到m 的那個人出列,它的下一位又從1開始報數,數到m的那個人又出列,依次類推,直到所有人出列爲止,由此產生一個出隊編號的序列。
        
    2) 提示:用一個不帶頭結點的循環鏈表來處理Josephu 問題:先構成一個有n個結點的單循環鏈表(單向環形鏈表),然後由k結點起從1開始計數,計到m時,對應結點從鏈表中刪除,然後再從被刪除結點的下一個結點又從1開始計數,直到最後一個結點從鏈表中刪除算法結束。
        
    3) 小結:完成約瑟夫問題,需要使用到單向環形鏈表這個數據結構
  • 其它常見算法問題
1) 修路問題  => 最小生成樹(加權值)【數據結構】+ 普利姆算法
2) 最短路徑問題  =>+弗洛伊德算法
3) 漢諾塔 => 分支算法 
4) 八皇后問題 => 回溯法

思維導圖總結

在這裏插入圖片描述

線性結構和非線性結構

數據結構包括:線性結構非線性結構

  • 線性結構

    • 線性結構作爲最常用的數據結構,其特點是數據元素之間存在一對一的線性關係。
    • 線性結構有兩種不同的存儲結構,即順序存儲結構和鏈式存儲結構。順序存儲的線性表稱爲順序表,順序表中的存儲元素是連續的
    • 鏈式存儲的線性表稱爲鏈表,鏈表中的存儲元素不一定是連續的,元素節點中存放數據元素以及相鄰元素的地址信息。
    • 線性結構常見的有:數組、隊列、鏈表和棧,後面會詳細敘述。
  • 非線性結構

    • 非線性結構包括:二維數組,多維數組,廣義表,樹結構,圖結構

第二章 稀疏數組與隊列

稀疏數組的應用場景

先看一個實際的需求

​ 1. 編寫的五子棋程序中,有存盤退出續上盤的功能。

在這裏插入圖片描述

分析問題: 
	因爲該二維數組的很多值是默認值0, 因此記錄了很多沒有意義的數據.->稀疏數組。
  • 稀疏數組基本介紹
    • 當一個數組中大部分元素爲0,或者爲同一個值的數組時,可以使用稀疏數組來保存該數組。
    • 稀疏數組的處理方法是:
      • 記錄數組一共有幾行幾列,有多少個不同的值;
      • 把具有不同值的元素的行列及值記錄在一個小規模的數組中,從而縮小程序的規模。
  • 稀疏數組舉例說明

在這裏插入圖片描述

	/*
	 * 二維數組轉稀疏數組過程
	 * 
	 * 將原始稀疏數組用二維數組存儲,需要6行7列的數組,記錄42個數據;
	 * 如果使用稀疏數組存儲,在稀疏數組的第一行第一列記錄原始數組總行數,
	 * 第一行第二列記錄原始數組的總列數,第一行第三列記錄原始數組非零值的個數,
	 * 在稀疏數組的第二行開始的每一行分別記錄每一個非零值的行值、列值、具體數據大小,
	 * 使用稀疏數組即可將原始數組由6行7列42個值的二維數組,
	 * 轉換爲9行3列27個值的二維數組。
	 */

稀疏數組轉換的思路分析及實現

  • 應用實例

    • 使用稀疏數組,來保留類似前面的二維數組(棋盤、地圖等等)
    • 把稀疏數組存盤,並且可以從新恢復原來的二維數組數
    • 整體思路分析

在這裏插入圖片描述

二維數組 轉 稀疏數組的思路
1. 遍歷  原始的二維數組,得到有效數據的個數 sum
2. 根據sum 就可以創建 稀疏數組 sparseArr   int[sum + 1] [3]
3. 將二維數組的有效數據數據存入到 稀疏數組

稀疏數組轉原始的二維數組的思路

1. 先讀取稀疏數組的第一行,根據第一行的數據,創建原始的二維數組,比如上面的  chessArr2 = int [11][11]
2. 在讀取稀疏數組後幾行的數據,並賦給原始的二維數組,即可.

  • 代碼實現
public class SparceArray {

	public static void main(String[] args) {
		
		//創建一個原始的二維數組 11*11
		// 0:表示沒有棋子,1表示黑子,2表示白子
		int chessArr[][] = new int[11][11];
		chessArr[1][3] = 5;
		chessArr[2][1] = 17;
		chessArr[5][5] = 21;
		//輸出原始二維數組
		System.out.println("原始的二維數組:");
		for(int[] row : chessArr){
			for(int data : row){
				System.out.printf("%d\t",data);
			}
			System.out.println();
		}
		
		//二維數組 轉 稀疏數組
		//1.先遍歷二維數組,得到非0數據的個數
		int sum = 0;
		for(int i = 0;i < 11;i++){
			for(int j = 0;j < 11;j++){
				if(chessArr[i][j] != 0){
					sum++;
				}
			}
		}
		
		//2.創建對應的稀疏數組
		int sparseArr[][] = new int[sum + 1][3];	//除去第一行
		//給稀疏數組賦值
		sparseArr[0][0] = 11;
		sparseArr[0][1] = 11;
		sparseArr[0][2] = sum;	//有效數據的個數,即非0的值
		
		//遍歷二維數組,將非0的值存入到稀疏數組saprseArr中
		int count = 0;	//用於記錄是第幾個非0數據
		for(int i = 0;i < 11;i++){
			for(int j = 0;j < 11;j++){
				if(chessArr[i][j] != 0){
					count++;
					sparseArr[count][0] = i;	//第一列
					sparseArr[count][1] = j;	//第二列
					sparseArr[count][2] = chessArr[i][j];	//第三列
				}
			}
		}
		
		//輸出稀疏數組的形式
		System.out.println();
		System.out.println("得到的稀疏數組爲: ");
		for(int i = 0;i < sparseArr.length;i++){
			System.out.printf("%d\t%d\t%d\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
		}
		System.out.println();
		
		//將稀疏數組恢復爲原始的二維數組
		/*
		 * 1.先讀取稀疏數組的第一行,根據第一行的數據,創建原始的二維數組,比如上面的  chessArr2 = int [11][11]
		 * 2.在讀取稀疏數組後幾行的數據,並賦給 原始的二維數組 即可.
		 */
		//1.先讀取稀疏數組的第一行,根據第一行的數據,創建原始的二維數組
		int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
		
		//2.在讀取稀疏數組後幾行的數據(從第二行開始),並賦給 原始的二維數組
		for(int i = 1;i < sparseArr.length;i++){
			chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
		}
		
		//輸出恢復後的二維數組
		System.out.println();
		System.out.println("恢復後的二維數組:");
		for(int[] row : chessArr2){
			for(int data : row){
				System.out.printf("%d\t",data);
			}
			System.out.println();
		}
	
	}

}
  • 課後練習
import java.awt.Desktop;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/*
 * 要求:
 * 	1.在前面的基礎上,將稀疏數組保存到磁盤上,比如 map.data
 *  2.恢復原來的數組時,讀取map.data 進行恢復
 */
public class SparceArray {

	public static void main(String[] args) throws Exception {
		
		//創建一個原始的二維數組 11*11
		// 0:表示沒有棋子,1表示黑子,2表示白子
		int chessArr[][] = new int[11][11];
		chessArr[1][3] = 5;
		chessArr[2][1] = 17;
		chessArr[5][5] = 21;
		//輸出原始二維數組
		System.out.println("原始的二維數組:");
		for(int[] row : chessArr){
			for(int data : row){
				System.out.printf("%d\t",data);
			}
			System.out.println();
		}
		
		//二維數組 轉 稀疏數組
		//1.先遍歷二維數組,得到非0數據的個數
		int sum = 0;
		for(int i = 0;i < 11;i++){
			for(int j = 0;j < 11;j++){
				if(chessArr[i][j] != 0){
					sum++;
				}
			}
		}
		
		//2.創建對應的稀疏數組
		int sparseArr[][] = new int[sum + 1][3];	//除去第一行
		//給稀疏數組賦值
		sparseArr[0][0] = 11;
		sparseArr[0][1] = 11;
		sparseArr[0][2] = sum;	//有效數據的個數,即非0的值
		
		//遍歷二維數組,將非0的值存入到稀疏數組saprseArr中
		int count = 0;	//用於記錄是第幾個非0數據
		for(int i = 0;i < 11;i++){
			for(int j = 0;j < 11;j++){
				if(chessArr[i][j] != 0){
					count++;
					sparseArr[count][0] = i;	//第一列
					sparseArr[count][1] = j;	//第二列
					sparseArr[count][2] = chessArr[i][j];	//第三列
				}
			}
		}
		
		//保存稀疏數組
		File file = new File("F:\\java\\Data structure\\day01\\map.data");
		FileOutputStream fos = new FileOutputStream(file);

		OutputStreamWriter write = new OutputStreamWriter(fos, "UTF-8");
		
		//輸出稀疏數組的形式
		System.out.println();
		System.out.println("得到的稀疏數組爲: ");
		for(int i = 0;i < sparseArr.length;i++){
			System.out.printf("%d\t%d\t%d\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
			
			if (i == sparseArr.length - 1) {
				write.append(sparseArr[i][0] + "," + sparseArr[i][1] + "," + sparseArr[i][2]);
			} else {
				write.append(sparseArr[i][0] + "," + sparseArr[i][1] + "," + sparseArr[i][2] + ",");
			}

		}
		
		System.out.println("寫入文件中...");
		write.close();
		fos.close();

		System.out.println("打開文件中...");
		Desktop.getDesktop().open(file);

		System.out.println("-------------先讀取map.data-----------------");
		// 創建 FileReader 對象
		FileInputStream fis = new FileInputStream(file);

		InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
		StringBuffer sb = new StringBuffer();
		while (reader.ready()) {
			sb.append((char) reader.read());// 轉成char加到StringBuffer對象中
		}

		System.out.println(sb.toString());
		reader.close();// 關閉讀取流
		fis.close();// 關閉輸入流,釋放系統資源

		System.out.println("-------------恢復成稀疏數組sparseArr-----------------");
		
		System.out.println();
		
		//將稀疏數組恢復爲原始的二維數組
		/*
		 * 1.先讀取稀疏數組的第一行,根據第一行的數據,創建原始的二維數組,比如上面的  chessArr2 = int [11][11]
		 * 2.在讀取稀疏數組後幾行的數據,並賦給 原始的二維數組 即可.
		 */
		//1.先讀取稀疏數組的第一行,根據第一行的數據,創建原始的二維數組
		int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
		
		//2.在讀取稀疏數組後幾行的數據(從第二行開始),並賦給 原始的二維數組
		for(int i = 1;i < sparseArr.length;i++){
			chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
		}
		
		//輸出恢復後的二維數組
		System.out.println();
		System.out.println("恢復後的二維數組:");
		for(int[] row : chessArr2){
			for(int data : row){
				System.out.printf("%d\t",data);
			}
			System.out.println();
		}
	
	}
	
}

隊列的應用場景和介紹

隊列的一個使用場景

銀行排隊的案例:

在這裏插入圖片描述

  • 隊列介紹

    • 隊列是一個有序列表,可以用數組或是鏈表來實現。

    • 遵循先入先出的原則。即:先存入隊列的數據,要先取出。後存入的要後取出

    • 示意圖:(使用數組模擬隊列示意圖)

在這裏插入圖片描述

    第一幅圖:隊列初始的情況
    	Queue--》代表類Queue
    	rear --》代表隊尾,初始化爲-1
    	front--》代表隊首,初始化爲-1
        MaxSize-1--》隊列的最大容量(0開始計數,需減一)
    第二幅圖:向隊列增加數據的情況
        當數據增加時rear變大,front不變    
    第三幅圖:從隊列取數據的情況
        當數據取出時front變大,rear不變

數組模擬隊列的思路分析及實現

  • 數組模擬隊列

    • 隊列本身是有序列表,若使用數組的結構來存儲隊列的數據,則隊列數組的聲明如下圖, 其中 maxSize 是該隊列的最大容量。

    • 因爲隊列的輸出、輸入是分別從前後端來處理,因此需要兩個變 frontrear分別記錄隊列前後端的下標,front 會隨着數據輸出而改變,而 rear則是隨着數據輸入而改變,如圖所示:

在這裏插入圖片描述

  • 當我們將數據存入隊列時稱爲”addQueue”,addQueue 的處理需要有兩個步驟:

  • 思路分析

    • 將尾指針往後移:rear+1 , 當front == rear 【空】
    • 若尾指針 rear 小於隊列的最大下標 maxSize-1,則將數據存入 rear所指的數組元素中,否則無法存入數據。 rear == maxSize - 1[隊列滿]
  • 代碼實現

import java.util.Scanner;

public class ArrayQueueDemo {

	public static void main(String[] args) {
		
		//測試代碼
		//創建一個隊列
		ArrayQueue queue = new ArrayQueue(3);
		char key = ' ';	//接收用戶輸入
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		//輸出一個菜單
		while(loop){
			System.out.println("s(show):顯示隊列");
			System.out.println("e(exit):退出程序");
			System.out.println("a(add):添加數據到隊列");
			System.out.println("g(get):從隊列中取出數據");
			System.out.println("h(head):查看隊列頭的數據");
			key = scanner.next().charAt(0);	//接收一個字符
			switch(key){
			case 's':
				queue.showQueue();
				break;
			case 'a':
				System.out.println("輸出一個數");
				int value = scanner.nextInt();
				queue.addQueue(value);
				break;
			case 'h':	//查看隊列頭的數據
				try {
					int res = queue.headQueue();
					System.out.printf("隊列頭的數據是%d\n",res);
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}				
				break;
			case 'g':	//取出數據
				try {
					int res = queue.getQueue();
					System.out.printf("取出的數據是%d\n",res);
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case 'e':	//退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出-------");
	}

}

// 使用數組模擬隊列---》編寫一個ArrayQueue類
class ArrayQueue {
	private int maxSize; // 表示數組的最大容量
	private int front; // 隊列頭
	private int rear; // 隊列尾
	private int[] arr; // 該數組用於存放數據,模擬隊列

	// 創建隊列的構造器
	public ArrayQueue(int arrMaxSize) {
		maxSize = arrMaxSize;
		arr = new int[maxSize];
		front = -1; // 指向隊列頭部,分析出front是指向隊列頭的前一個位置
		rear = -1; // 指向隊列尾部,指向隊列尾的數據(即隊列尾部的最後一個數據)
	}

	// 判斷隊列是否滿
	public boolean isFull() {
		return rear == maxSize - 1; // 滿爲true,不滿爲false
	}

	// 判斷隊列是否爲空
	public boolean isEmpty() {
		return rear == front; // 爲空即true,不空爲false
	}

	// 添加數據到隊列
	public void addQueue(int n) {
		// 判斷隊列是否滿,滿了不加入,未滿則加入數據
		if (isFull()) { // 隊列滿了
			System.out.println("隊列滿了,不能再加了!!!");
			return;
		}
		// 隊列未滿
		rear++; // 讓rear後移
		arr[rear] = n; // 添加數據
	}

	// 獲取隊列的數據,出隊列
	public int getQueue() {
		// 判斷隊列是否空
		if (isEmpty()) { // 隊列爲空
			// 錯誤的寫法:
			// return -1; //當要出的數據爲-1時,此處不正確,應當通過如下方法
			// 正確的寫法:通過拋出異常來處理
			throw new RuntimeException("隊列空了,不能取數據。");
		}
		// 隊列不空,返回數據
		front++; // 讓front後移
		return arr[front]; // 出隊列
	}

	// 顯示隊列的所有數據
	public void showQueue() {
		// 遍歷
		if (isEmpty()) { // 隊列爲空
			System.out.println("隊列空的,沒有數據。");
			return;
		}
		// 隊列不空
		for (int i = 0; i < arr.length; i++) {
			System.out.printf("arr[%d]=%d\n", i, arr[i]);
		}
	}

	// 顯示隊列的頭數據。注意:不是取出數據
	public int headQueue() {
		// 判斷
		if (isEmpty()) {
			throw new RuntimeException("隊列空了,不能取數據。");
		}
		return arr[front + 1];
	}
}
  • 問題分析並優化

1)目前數組使用一次就不能用,無法達到複用的效果;

2)將這個數組使用算法,改進成一個環形的隊列取模:%

具體如下:

數組模擬環形隊列思路分析及實現

對前面的數組模擬隊列的優化,充分利用數組. 因此將數組看做是一個環形的。(通過取模的方式來實現即可)

  • 分析說明:

    • 尾索引的下一個爲頭索引時表示隊列滿,即將
      列容量空出一個作爲約定
      ,這個在做判斷隊列滿的
      時候需要注意 (rear + 1) % maxSize == front 滿]

    • rear == front [空]

    • 測試示意圖:

在這裏插入圖片描述

```java
思路如下:
1.  front 變量的含義做一個調整: front 就指向隊列的第一個元素, 也就是說 arr[front] 就是隊列的第一個元素 
front 的初始值 = 0
2.  rear 變量的含義做一個調整:rear 指向隊列的最後一個元素的後一個位置. 因爲希望空出一個空間做爲約定.
rear 的初始值 = 0
3. 當隊列滿時,條件是  (rear  + 1) % maxSize == front 【滿】
4. 對隊列爲空的條件, rear == front 空
5. 當我們這樣分析, 隊列中有效的數據的個數   (rear + maxSize - front) % maxSize   // rear = 1 front = 0 
6. 綜上,就可以在原來的隊列上修改得到,一個環形隊列。
```
  • 代碼實現:
import java.util.Scanner;

public class CircleArrayQueueDemo {

	public static void main(String[] args) {
		
		// 測試代碼
		System.out.println("測試數組模擬環形隊列-------");
		// 創建一個環形隊列
		CircleArray queue = new CircleArray(6);	//這裏設置的6,是其隊列的有效數據最大是5
		char key = ' '; // 接收用戶輸入
		Scanner scanner = new Scanner(System.in);
		boolean loop = true;
		// 輸出一個菜單
		while (loop) {
			System.out.println("s(show):顯示隊列");
			System.out.println("e(exit):退出程序");
			System.out.println("a(add):添加數據到隊列");
			System.out.println("g(get):從隊列中取出數據");
			System.out.println("h(head):查看隊列頭的數據");
			key = scanner.next().charAt(0); // 接收一個字符
			switch (key) {
			case 's':
				queue.showQueue();
				break;
			case 'a':
				System.out.println("輸出一個數");
				int value = scanner.nextInt();
				queue.addQueue(value);
				break;
			case 'h': // 查看隊列頭的數據
				try {
					int res = queue.headQueue();
					System.out.printf("隊列頭的數據是%d\n", res);
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case 'g': // 取出數據
				try {
					int res = queue.getQueue();
					System.out.printf("取出的數據是%d\n", res);
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case 'e': // 退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出-------");

	}

}

// 使用數組模擬隊列---》編寫一個ArrayQueue類
class CircleArray {
	private int maxSize; // 表示數組的最大容量

	// front 隊列頭,front 就指向隊列的第一個元素, 也就是說 arr[front] 就是隊列的第一個元素
	// front 的初始值爲0
	private int front;

	// rear 隊列尾,rear 指向隊列的最後一個元素的後一個位置. 因爲希望空出一個空間做爲約定.
	// rear 的初始值 = 0
	private int rear;
	private int[] arr; // 該數組用於存放數據,模擬隊列

	// 創建隊列的構造器
	public CircleArray(int arrMaxSize) {
		maxSize = arrMaxSize;
		arr = new int[maxSize];
	}

	// 判斷隊列是否滿
	public boolean isFull() {
		return (rear + 1) % maxSize == front; // 滿爲true,不滿爲false
	}

	// 判斷隊列是否爲空
	public boolean isEmpty() {
		return rear == front; // 爲空即true,不空爲false
	}

	// 添加數據到隊列
	public void addQueue(int n) {
		// 判斷隊列是否滿,滿了不加入,未滿則加入數據
		if (isFull()) { // 隊列滿了
			System.out.println("隊列滿了,不能再加了!!!");
			return;
		}
		// 隊列未滿
		arr[rear] = n; // 直接添加數據
		rear = (rear + 1) % maxSize; // 將 rear 後移, 這裏必須考慮取模
	}

	// 獲取隊列的數據,出隊列
	public int getQueue() {
		// 判斷隊列是否空
		if (isEmpty()) { // 隊列爲空
			// 通過拋出異常來處理
			throw new RuntimeException("隊列空了,不能取數據。");
		}
		// 隊列不空,返回數據
		// 1. 先把 front 對應的值保留到一個臨時變量
		// 2. 將 front 後移, 考慮取模
		// 3. 將臨時保存的變量返回
		int value = arr[front];
		front = (front + 1) % maxSize;
		return value;
	}

	// 顯示隊列的所有數據
	public void showQueue() {
		// 遍歷
		if (isEmpty()) { // 隊列爲空
			System.out.println("隊列空的,沒有數據。");
			return;
		}
		// 隊列不空
		// 思路:從front開始遍歷,遍歷多少個元素
		for (int i = front; i < front + size(); i++) {
			System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
		}
	}

	// 求出當前隊列有效數據的個數
	public int size() {
		// rear = 2
		// front = 1
		// maxSize = 3
		return (rear + maxSize - front) % maxSize;
	}

	// 顯示隊列的頭數據。注意:不是取出數據
	public int headQueue() {
		// 判斷
		if (isEmpty()) {
			throw new RuntimeException("隊列空了,不能取數據。");
		}
		return arr[front];
	}
}

思維導圖總結

在這裏插入圖片描述

Leetcode每日一練

數據結構和算法不單單是理論學習,還需要相應的實踐練習。

1. 兩數之和

  • 給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。

  • 你可以假設每種輸入只會對應一個答案。但是,數組中同一個元素不能使用兩遍。

示例:

給定 nums = [2, 7, 11, 15], target = 9

因爲 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:

1.創建一個map
2.for循環遍歷nums數組
3.用target減nums[i],以計算哪個數能和當前的數相加可得到target
4.判斷map裏是否有這個數
    如果有,返回結果;
    如果沒有,則把nums[i]當作key,i當作value放入map中。

AC

class Solution {
    public int[] twoSum(int[] nums, int target) {
    	Map<Integer, Integer> map = new HashMap<>();
    	for(int i = 0;i < nums.length;i++){
    		// 如果 map 存在此差值,則返回
    		if(map.containsKey(target - nums[i])){
    			return new int[]{i,map.get(target - nums[i])};
    		}
    		map.put(nums[i], i);	// 將該數組的值存入 map
    	}
		return null;
    }
}

5. 最長迴文子串

  • 給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。

示例 1:

輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。

示例 2:

輸入: "cbbd"
輸出: "bb"

思路:

1.如果字符串長度小於2或者爲空,直接返回原字符串
2.定義三個變量,一個start存儲當前找到的最大回文字符串的起始位置,另一個maxLen記錄字符串的長度,第三個變量end記錄迴文字符串的終止位置。  
3.定義一個 boolean df[i][j] 來判斷字符串從 i 到 j 這段是否爲迴文。
    如果 df[i][j]=true,則需要判斷 df[i-1][j+1] 是否爲迴文,那麼就只需要判斷字符串在(i-1)和(j+1)兩個位置是否爲相同的字符即可。也就是當df[i][j]=true 並且(i-1)和(j+1)兩個位置爲相同的字符,此時 dp[i-1][j+1]=true。 
    
附:
  長度爲奇數的迴文串,比如a, aba, abcba,以字母爲中心
  長度爲偶數的迴文串,比如aa, abba,以兩個字母之間空隙爲中心

AC

public class Solution {
	public String longestPalindrome(String s) {

		if (null == s || s.length() < 2) {
			return s;
		}
		int start = 0; // 記錄迴文子串的開始位置
		int maxLen = 1; // 記錄字符串的長度
		int end = 0;

		// 定義二維數組記錄原字符串 i 到 j 區間是否爲迴文子串。
		boolean[][] df = new boolean[s.length()][s.length()];
		// 遍歷元素並得到包含當前元素之前字符串的最大回文子串。
		for (int i = 1; i < s.length(); i++) {
			for (int j = 0; j < i; j++) {
				// 狀態轉移,判斷記錄 i 到 j 位置是否爲迴文子串。
				if (s.charAt(j) == s.charAt(i) && (i - j <= 2 || df[i - 1][j + 1])) {
					df[i][j] = true;
					// 判斷更新記錄遍歷過的最長迴文子串。
					if (i - j + 1 > maxLen) {
						maxLen = i - j + 1;
						start = j;
						end = i;
					}
				}
			}
		}
		return s.substring(start, end + 1);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章