文章目錄
第一章 數據結構和算法概述
幾個經典的算法面試題
- 例題1——字符串匹配問題
字符串匹配問題:
1) 有一個字符串 str1= "結構與算法 數據和結構 數據結構與算法數據",和一個子串 str2="數據結構與算法"
2) 現在要判斷str1是否含有str2, 如果存在,就返回第一次出現的位置, 如果沒有,則返回-1
3)要求用最快的速度來完成匹配
4)你的思路是什麼?
思路:
• 暴力匹配(一個字符一個字符一一對應匹配)
• KMP算法《部分匹配表》(先知道有這個東西即可!!!)
- 例題2——漢諾塔遊戲
漢諾塔遊戲, 請完成漢諾塔遊戲的代碼: 要求:
1) 將A塔的所有圓盤移動到C塔。並且規定,在2) 小圓盤上不能放大圓盤,3)在三根柱子之間一次只能移動一個圓盤
操作步驟:
三個柱子!分別爲1號 2號 3號
五個盤子 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 得出
用編程的方式:使用到分治算法.
- 例3——八皇后問題
八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即:任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。【92】
用編程的方式:使用到分治算法或回溯算法
高斯認爲有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。計算機發明後,有多種計算機語言可以解決此問題
- 例4——馬踏棋盤算法
馬踏棋盤算法介紹和遊戲演示
1) 馬踏棋盤算法也被稱爲騎士周遊問題
2) 將馬隨機放在國際象棋的8×8棋盤Board[0~7][0~7]的某個方格中,馬按走棋規則(馬走日字)進行移動。要求每個方格只進入一次,走遍棋盤上全部64個方格
3) 遊戲演示: http://www.4399.com/flash/146267_2.htm
4) 用編程的方式:會使用到圖的深度優化遍歷算法(DFS) + 貪心算法優化
數據結構和算法的重要性
-
算法是程序的靈魂,優秀的程序可以在海量數據計算時,依然保持高速計算。
-
一般來講 程序會使用了內存計算框架(比如Spark)和緩存技術(比如Redis等)來優化程序,再深入的思考一下,這些計算框架和緩存技術, 它的核心功能是哪個部分呢?
-
拿實際工作經歷來說, 在Unix下開發服務器程序,功能是要支持上千萬人同時在線, 在上線前,做內測,一切OK,可上線後,服務器就支撐不住了, 公司的CTO對代碼進行優化,再次上線,堅如磐石。你就能感受到程序是有靈魂的,就是算法。
-
目前程序員面試的門檻越來越高,很多一線IT公司(大廠),都會有數據結構和算法面試題(負責的告訴你,肯定有的)。
-
如果你不想永遠都是代碼工人,那就花時間來研究下數據結構和算法。
數據結構與算法框架
這個只是大體框架圖,後續會逐漸修正,不足之處多多斧正!!!
數據結構和算法的關係(瞭解!!!)
- 數據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 問題爲:設編號爲1,2,… 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 是該隊列的最大容量。
-
因爲隊列的輸出、輸入是分別從前後端來處理,因此需要兩個變量 front及rear分別記錄隊列前後端的下標,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);
}
}