面試常見手撕程式碼題

拽根胡來發表於2019-10-13

堆排序

www.jianshu.com/p/11655047a…

(升序排序)整體思路是:

  1. 初始陣列表示一個未排序的原始堆
  2. 構造一個最大堆
  3. 依次將堆頂元素交換到堆尾,調整使其保持最大堆,堆尾指標前移,直到所有元素有序(堆中之剩下堆頂元素)

快速排序

快排的核心部分在於partition的實現。partition需要找到一個元素的正確位置,將其移動到這個位置,並返回這個位置。

歸併排序

www.cnblogs.com/chengxiao/p…

歸併排序的核心在於分治。分就是把原陣列一分為二,然後再分,直到每一部分只剩下最多一個元素;並就是把結果合併,當每一部分最多有一個元素的時候可以直接交換,而多於一個元素的時候可以將有序的兩部分從頭到尾比較排序。

在合併的時候會用到額外的空間,因此可以在一開始就定義一塊和原陣列相同大小的空間用於合併,避免遞迴過程中頻繁開闢新的空間。

連結串列

反轉連結串列(遞迴和非遞迴)

leetcode-cn.com/problems/re…

非遞迴(頭插法)

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...?

blog.csdn.net/xgjonathan/…

結論:

  1. 無論步長為多少,快慢指標在環裡都能相遇
  2. 步長設定為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 步後相遇,而這個相遇結點正好就是環的起始結點。

二叉樹

二叉樹的遞迴遍歷以及(前中後序)非遞迴遍歷

www.cnblogs.com/dolphin0520…

二叉樹的層序遍歷(使用/不使用額外的資料結構)

blog.csdn.net/m0_37925202…

圖(矩陣)的深度優先和廣度優先遍歷

blog.csdn.net/jeffleo/art…

動態規劃

最長公共子串

segmentfault.com/a/119000000…

最長遞增子序列

blog.csdn.net/u013178472/…

最長迴文串

blog.csdn.net/kangroger/a…

實現一個LRU的Cache

設計一個類LRUCache,滿足以下條件:

  1. 這個類的建構函式應該傳入一個int型別的引數size,表示最多可容納多少個元素
  2. 包含方法put、get。put是放入新元素,get是獲取某一個元素
  3. 當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());
            }
            
        }
    }
}
複製程式碼

相關文章