堆排序
(升序排序)整體思路是:
- 初始陣列表示一個未排序的原始堆
- 構造一個最大堆
- 依次將堆頂元素交換到堆尾,調整使其保持最大堆,堆尾指標前移,直到所有元素有序(堆中之剩下堆頂元素)
快速排序
快排的核心部分在於partition的實現。partition需要找到一個元素的正確位置,將其移動到這個位置,並返回這個位置。
歸併排序
歸併排序的核心在於分治。分就是把原陣列一分為二,然後再分,直到每一部分只剩下最多一個元素;並就是把結果合併,當每一部分最多有一個元素的時候可以直接交換,而多於一個元素的時候可以將有序的兩部分從頭到尾比較排序。
在合併的時候會用到額外的空間,因此可以在一開始就定義一塊和原陣列相同大小的空間用於合併,避免遞迴過程中頻繁開闢新的空間。
連結串列
反轉連結串列(遞迴和非遞迴)
非遞迴(頭插法)
public ListNode reverseList(ListNode head) {
ListNode newHead = null;
while(head!=null){
ListNode t = head.next;
head.next = newHead;
newHead = head;
head = t;
}
return newHead;
}
複製程式碼
遞迴(頭插法)
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
public ListNode reverse(ListNode newHead, ListNode head) {
if(head==null){
return newHead;
}
else{
ListNode t = head.next;
head.next = newHead;
newHead = head;
return reverse(newHead, t);
}
}
複製程式碼
倒數第K個節點
使用雙指標的時候要注意連結串列的長度,要考慮第二個指標在第一次前進k個節點時出現null的情況。
檢測環
使用快慢指標。
引申問題1:為什麼快指標的步長是2,而不是3、4、5...?
結論:
- 無論步長為多少,快慢指標在環裡都能相遇
- 步長設定為2能最快確定有環
引申問題2:如何確定環的入口?
s:從起始點到環入口的距離
cl:環長度
當慢指標剛好進入環中,也就是慢指標走了 s 步之後,快指標走了 2s 步,所以快指標在環中走了 2s - s = s 步; 由於存在 s > cl 的情況,我們記快指標超出 ks(k為自然數) 的距離是 s % cl ; 此時,快指標需要追及慢指標的距離是 cl - s % cl; 因此,當慢指標在環中走了cl - s % cl 步後,快指標追上了慢指標;
所以,相遇之後的慢指標距離 ks 的距離是 cl - (cl - s % cl) = s % cl 。因為有環的存在,我們可以把這個距離看成 s + M * cl,M 是正整數。所以相遇時的慢指標距離環的起始結點ks 是 s 。這時,我們再設定另一個指標從單向連結串列的頭開始,以步長為 1 移動,移動 s 步後相遇,而這個相遇結點正好就是環的起始結點。
二叉樹
二叉樹的遞迴遍歷以及(前中後序)非遞迴遍歷
二叉樹的層序遍歷(使用/不使用額外的資料結構)
圖(矩陣)的深度優先和廣度優先遍歷
動態規劃
最長公共子串
最長遞增子序列
最長迴文串
實現一個LRU的Cache
設計一個類LRUCache,滿足以下條件:
- 這個類的建構函式應該傳入一個int型別的引數size,表示最多可容納多少個元素
- 包含方法put、get。put是放入新元素,get是獲取某一個元素
- 當LRUCache滿了之後刪除最近最久未使用的元素。
1. 使用Java自帶的LinkedHashMap
這種方法比較簡單,就是維護一個LinkedHashMap,重寫它的removeEldestEntry方法。
LinkedHashMap的三個構造引數分別表示初始大小、擴張因子、使用accessOrder排序。如果不指定第三個引數,則會按照新增的順序排序。
public class LRUCache {
private LinkedHashMap<String,String> cache;
private int maxsize;
public LRUCache(int maxsize){
this.maxsize = maxsize;
this.cache = new LinkedHashMap<String,String>(16,0.75f,true){
@Override
protected boolean removeEldestEntry(Map.Entry<String,String> eldest) {
return this.size()>maxsize;
}
};
}
public void put(String key,String value){
cache.put(key,value);
}
public String get(String key){
return cache.get(key);
}
}
複製程式碼
2. 僅使用Java中的HashMap和Deque
本質上LRUCache就是一個HashMap,只不過需要使元素之間維持一定的順序。LinkedHashMap實現了這個功能。如果不使用LinkedHashMap的話,則需要使用與其類似的思路,使用HashMap儲存鍵值對,然後使用一個雙端佇列來記錄訪問順序。查詢的時候需要先從佇列中找到對應的key,完成一系列出隊、入隊操作最後再將這個key入隊。
public class LRUCache {
private HashMap<String,String> cache;
private Deque<String> orderQueue;
private int maxsize;
public LRUCache(int maxsize){
this.maxsize = maxsize;
this.orderQueue = new ArrayDeque<>();
this.cache = new HashMap<>();
}
public void put(String key,String value){
if(cache.size()>=maxsize){
String eldestKey = orderQueue.poll();
cache.remove(eldestKey);
}
cache.put(key,value);
orderQueue.offer(key);
}
public String get(String key){
if(cache.size()<1||!cache.containsKey(key)){
return null;
}
Deque<String> stack = new ArrayDeque<String>();
String e = null;
while(!(e=orderQueue.poll()).equals(key)){
stack.push(e);
}
while(!stack.isEmpty()){
orderQueue.offerFirst(stack.pop());
}
orderQueue.offer(e);
return cache.get(key);
}
}
複製程式碼
實現一個阻塞佇列
阻塞佇列的實現需要保證多執行緒圍繞隊滿/隊空這兩個條件來協作。當隊滿時,新增元素的執行緒應當阻塞;當隊空時,獲取元素的執行緒應當阻塞。
public class MyBlockingQueue<T>{
private volatile List<T> queue;
private int size;
public MyBlockingQueue(int size){
queue = new ArrayList<>();
this.size = size;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size()>=size){
wait();
}
if(queue.size()==0){
// 如果為0,則可能有其他執行緒在阻塞get,因此呼叫notifyAll
notifyAll();
}
queue.add(element);
}
public synchronized T get() throws InterruptedException {
while(queue.isEmpty()){
wait();
}
if(queue.size()>=size){
// 如果為size,則可能有其他執行緒在阻塞put,因此呼叫notifyAll
notifyAll();
}
return queue.remove(0);
}
}
複製程式碼
實現生產者/消費者模型
使用上面的阻塞佇列實現:
public class ProducerConsumer{
private static MyBlockingQueue<Integer> queue;
static class Producer extends Thread{
private MyBlockingQueue<Integer> queue;
public Producer(MyBlockingQueue<Integer> queue){
super();
this.queue = queue;
}
@Override
public void run(){
Random rnd = new Random();
for(int i=0;i<100;i++) {
try {
queue.put(i);
System.out.println("Producing "+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer extends Thread{
private MyBlockingQueue queue;
public Consumer(MyBlockingQueue<Integer> queue){
super();
this.queue = queue;
}
@Override
public void run(){
while(true){
try {
System.out.println("Consuming "+queue.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(10);
Producer p = new Producer(queue);
Consumer c = new Consumer(queue);
p.start();
c.start();
}
}
複製程式碼
實現兩個執行緒交替列印
兩個執行緒圍繞一個合作變數flag進行合作。
需要將flag宣告為volatile,否則程式會卡住,這跟JMM有關係。理論上說,每次工作執行緒修改了flag都遲早會同步到主記憶體,但是如果while迴圈體為空的話,訪問flag會太頻繁導致JVM來不及將修改後的值同步到主記憶體,這樣一來程式就會一直卡在一個位置。
如果不使用volatile也是可以的,將迴圈體裡面加上一句System.out.print("")
就行了,這樣可以降低訪問flag的頻率,從而使JVM有空將工作記憶體中的flag和主記憶體中的flag進行同步。
public class PrintAlternately {
static volatile int flag = 0;
static class EvenThread extends Thread{
@Override
public void run(){
for(int i=0;i<101;i+=2){
while(flag!=0){}
System.out.println(i);
flag = 1;
}
}
}
static class OddThread extends Thread{
@Override
public void run(){
for(int i=1;i<100;i+=2){
while(flag!=1){}
System.out.println(i);
flag = 0;
}
}
}
public static void main(String[] args){
EvenThread e = new EvenThread();
OddThread o = new OddThread();
e.start();
o.start();
}
}
複製程式碼
檔案的輸入和輸出
最長見的方法是使用BufferedReader逐行(字元流)讀取檔案,使用FileWriter或者BufferedWriter(字元流)寫檔案,區別是後者提供了方法newLine來寫換行符,而前者需要手動寫入\n
或者\r\n
來換行。
如果要求寫檔案的時候是在檔案末尾新增,而不是整體覆蓋,則FileWriter建構函式需要傳入第二個引數true(表示使用append模式)。
public class WriterReader {
public static void main(String[] args){
try(BufferedReader reader = new BufferedReader(new FileReader("from.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("to.txt"));){
String line = null;
while((line=reader.readLine())!=null){
writer.write(line);
writer.newLine();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
字串轉整型
思路:應該儘快除錯出來一個轉化的程式,然後再考慮各種情況。面試時手撕程式碼時間非常有限,所以優先使程式碼能工作,而不是所有情況都考慮全了,發現時間不夠或者瘋狂調bug。
先是一個合法字串轉正整數的方法:
public int transform(String str){
int res = 0;
int base = 1;
for(int i=str.length()-1;i>-1;i--){
char ch = str.charAt(i);
res = res + base*(ch-'0');
base *= 10;
}
return res;
}
複製程式碼
然後再考慮下面這些情況:
- 正負號
- 小數點
- 溢位
- 非法字元
考慮所有問題後,寫出來的參考程式碼為:
public class Solution {
public static int transform(String str) throws Exception{
String input = str;
// 先判斷是否合法
if(!valid(str)){
throw new Exception("Invalid input string!");
}
// 判斷正負
boolean isNegative = false;
if(str.charAt(0)=='-'){
isNegative = true;
input = input.substring(1,input.length());
}
// 去掉前面的0
int t = 0;
while(input.charAt(t)=='0'){
t += 1;
if(t>input.length()-1){
return 0;
}
}
input = input.substring(t, input.length());
// 去掉小數點
int pointIndex = getPointIndex(input);
int extra = 0;
if(pointIndex>-1){
if(pointIndex==input.length()-1){
input = input.substring(0, input.length()-1);
}else if(input.charAt(pointIndex+1)>'4'){
extra = 1;
input = input.substring(0, pointIndex);
}else{
input = input.substring(0, pointIndex);
}
}
// 第一個字元是小數點,則輸出只能是0或1
if(input.length()<1){
return extra;
}
// 溢位判斷
String MAXPOS = Integer.MAX_VALUE+"";
String MAXNEG = Integer.MIN_VALUE+"";
if(isNegative){
String temp = '-'+input;
if(temp.length()>MAXNEG.length()){
throw new Exception("Overflow error!");
}else if(temp.length()==MAXNEG.length()){
if(temp.compareTo(MAXNEG)>0){
throw new Exception("Overflow error!");
}else if(temp.compareTo(MAXNEG)==0&&extra>0){
throw new Exception("Overflow error!");
}
}
}else{
if(input.length()>MAXPOS.length()){
throw new Exception("Overflow error!");
}else if(input.length()==MAXPOS.length()){
if(input.compareTo(MAXPOS)>0){
throw new Exception("Overflow error!");
}else if(input.compareTo(MAXPOS)==0&&extra>0){
throw new Exception("Overflow error!");
}
}
}
//當前的字串為合法的,無符號的,只包含數字的字串,可以直接轉化為數字(按需要新增負號)
int res = 0;
int base = 1;
for(int i=input.length()-1;i>-1;i--){
char ch = input.charAt(i);
res = res + base*(ch-'0');
base *= 10;
}
return res*(isNegative?-1:1);
}
public static int getPointIndex(String str){
for(int i=0;i<str.length();i++){
if(str.charAt(i)=='.'){
return i;
}
}
return -1;
}
public static boolean valid(String str){
if(str.charAt(0)=='-'&&str.length()>1){
return valid(str.substring(1, str.length()));
}
int nPoint = 0;
for(char ch:str.toCharArray()){
if(ch=='.'){
nPoint += 1;
if(nPoint>1){
return false;
}
}else if(ch>'9'||ch<'0'){
return false;
}
}
return true;
}
public static void main(String args[]){
//-2147483648 ~ 2147483647
String[] strs = new String[]{
"-2147483648.6", //Overflow error!
"2147483647.1", //2147483647
"0.67", //1
"adfasdf", //Invalid input string!
"2147483648", //Overflow error!
"1.1.5", //Invalid input string!
"123.", //123
".67" //1
};
for(String s:strs){
try{
System.out.println(transform(s));
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
}
複製程式碼