数据结构+算法 面试题

目录

前言

一、概述

1.什么是数据结构

2.什么是算法

二、链表

三、栈

1. 什么是栈

2. 括号验证

3. 最小栈

4.区间最大值

四、队列

快手面试题-推荐结果打散

五、二分查找

总结



前言

背景:主要介绍数据结构和常用算法,能够在工作或者面试中帮助到自己。

原因:

1、数据结构和算法工作中很少使用,所以只有面试时才突击复习,造成每次面试,都要进行一轮的复习,由于算法的特殊性和大厂对算法的逐年研究,造成题目五花八门,很难一时半会进行针对性复习,茫茫题海中只能刷刷leetcode常见题目,切复习一轮时间复杂度达很高,所以心理总是忐忑不安,没有足够自信,所以只有平时积累,经常复习才是唯一出路,除非你非常聪明,理解力很好,但是谁让自己天资一般呢。

2、算法不像其他知识点,看一眼就可以回忆起来大概,算法需要慢慢的理解,N遍的书写,浪费很多时间,所以本人决定记录一下自己复习数据结构和算法的笔记,以后可以经常回看,帮助自己快速复习知识点,以及在工作中很好的应用。


一、概述

1.什么是数据结构

定义:

数据结构(data structure)是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法索引技术有关。

分类:

  • 线性结构
  • 非线性结构

常用的数据结构

  1. ▪ 数组(Array)
  2. ▪ 栈( Stack)
  3. ▪ 队列(Queue)
  4. ▪ 链表( Linked List)
  5. ▪ 树( Tree)
  6. ▪ 图(Graph)
  7. ▪ 堆(Heap)
  8. ▪ 散列表(Hash)

常用算法

数据结构研究的内容:就是如何按一定的逻辑结构,把数据组织起来,并选择适当的存储表示方法把逻辑结构组织好的数据存储到计算机的存储器里。算法研究的目的是为了更有效的处理数据,提高数据运算效率。数据的运算是定义在数据的逻辑结构上,但运算的具体实现要在存储结构上进行。一般有以下几种常用运算: 

  1. 检索。检索就是在数据结构里查找满足一定条件的节点。一般是给定一个某字段的值,找具有该字段值的节点。 
  2. 插入。往数据结构中增加新的节点。 
  3. 删除。把指定的结点从数据结构中去掉。 
  4. 更新。改变指定节点的一个或多个字段的值。  
  5. 排序。把节点按某种指定的顺序重新排列。例如递增或递减。 

2.什么是算法

算法(Algorithm)定义:

  • 是指解题方案的准确而完整的描述
  • 是一系列解决问题的清晰指令
  • 算法代表着用系统的方法描述解决问题的策略机制。

也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度时间复杂度来衡量。

特征

一个算法应该具有以下五个重要的特征:

有穷性

(Finiteness)

算法的有穷性是指算法必须能在执行有限个步骤之后终止;

确切性

(Definiteness)

算法的每一步骤必须有确切的定义;

输入项

(Input)

一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;

输出项

(Output)

一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;

可行性

(Effectiveness)

算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性)。

要素

一、数据对象的运算和操作

计算机可以执行的基本操作是以指令的形式描述的。一个计算机系统能执行的所有指令的集合,成为该计算机系统的指令系统。一个计算机的基本运算和操作有如下四类: [1] 

1.算术运算:加减乘除等运算

2.逻辑运算:或、且、非等运算

3.关系运算:大于、小于、等于、不等于等运算

4.数据传输:输入、输出、赋值等运算 [1] 

二、算法的控制结构:

一个算法的功能结构不仅取决于所选用的操作,而且还与各操作之间的执行顺序有关。 [1] 

评定

同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。一个算法的评价主要从时间复杂度空间复杂度来考虑。

时间复杂度

算法的时间复杂度是指执行算法所需要的计算工作量。一般来说,计算机算法是问题规模n

的函数f(n),算法的时间复杂度也因此记做:

t(n) = o(f(n))

因此,问题的规模 n 越大,算法执行的时间的增长率与 f(n) 的增长率正相关,称作渐进时间复杂度(Asymptotic Time Complexity)。

空间复杂度

算法的空间复杂度是指算法需要消耗的内存空间。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。

正确性

算法的正确性是评价一个算法优劣的最重要的标准。

可读性

算法的可读性是指一个算法可供人们阅读的容易程度。 [1] 

鲁棒性

鲁棒性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。 [1]

方法

递推法

递推是序列计算机中的一种常用算法。它是按照一定的规律来计算序列中的每个项,通常是通过计算机前面的一些项来得出序列中的指定项的值。其思想是把一个复杂的庞大的计算过程转化为简单过程的多次重复,该算法利用了计算机速度快和不知疲倦的机器特点。

递归法

程序调用自身的编程技巧称为递归(recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

注意:

(1) 递归就是在过程或函数里调用自身;

(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。

穷举法

穷举法,或称为暴力破解法,其基本思路是:对于要解决的问题,列举出它的所有可能的情况,逐个判断有哪些是符合问题所要求的条件,从而得到问题的解。它也常用于对于密码的破译,即将密码进行逐个推算直到找出真正的密码为止。例如一个已知是四位并且全部由数字组成的密码,其可能共有10000种组合,因此最多尝试10000次就能找到正确的密码。理论上利用这种方法可以破解任何一种密码,问题只在于如何缩短试误时间。因此有些人运用计算机来增加效率,有些人辅以字典来缩小密码组合的范围。

贪心算法

贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。

用贪心法设计算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题, 通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。

贪婪算法是一种改进了的分级处理方法,其核心是根据题意选取一种量度标准,然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量,如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。这种能够得到某种量度意义下最优解的分级处理方法称为贪婪算法。

对于一个给定的问题,往往可能有好几种量度标准。初看起来,这些量度标准似乎都是可取的,但实际上,用其中的大多数量度标准作贪婪处理所得到该量度意义下的最优解并不是问题的最优解,而是次优解。因此,选择能产生问题最优解的最优量度标准是使用贪婪算法的核心。

一般情况下,要选出最优量度标准并不是一件容易的事,但对某问题能选择出最优量度标准后,用贪婪算法求解则特别有效。 [2] 

分治法

分治法是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。 [2] 

分治法所能解决的问题一般具有以下几个特征:

(1) 该问题的规模缩小到一定的程度就可以容易地解决;

(2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;

(3) 利用该问题分解出的子问题的解可以合并为该问题的解;

(4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

动态规划法

动态规划是一种在数学和计算机科学中使用的,用于求解包含重叠子问题的最优化问题的方法。其基本思想是,将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。动态规划的思想是多种算法的基础,被广泛应用于计算机科学和工程领域。

动态规划程序设计是对解最优化问题的一种途径、一种方法,而不是一种特殊算法。不像前面所述的那些搜索或数值计算那样,具有一个标准的数学表达式和明确清晰的解题方法。动态规划程序设计往往是针对一种最优化问题,由于各种问题的性质不同,确定最优解的条件也互不相同,因而动态规划的设计方法对不同的问题,有各具特色的解题方法,而不存在一种万能的动态规划算法,可以解决各类最优化问题。因此读者在学习时,除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,以丰富的想象力去建立模型,用创造性的技巧去求解。 [2] 

迭代法

迭代法也称辗转,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法(或者称为一次解法),即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。 [2] 

分支界限法

分枝界限法是一个用途十分广泛的算法,运用这种算法的技巧性很强,不同类型的问题解法也各不相同。

分支定界法的基本思想是对有约束条件最优化问题的所有可行解(数目有限)空间进行搜索。该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集(称为分支),并为每个子集内的解的值计算一个下界或上界(称为定界)。在每次分支后,对凡是界限超出已知可行解值那些子集不再做进一步分支,这样,解的许多子集(即搜索树上的许多结点)就可以不予考虑了,从而缩小了搜索范围。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限。因此这种算法一般可以求得最优解

贪心算法一样,这种方法也是用来为组合优化问题设计求解算法的,所不同的是它在问题的整个可能解空间搜索,所设计出来的算法虽其时间复杂度贪婪算法高,但它的优点是与穷举法类似,都能保证求出问题的最佳解,而且这种方法不是盲目的穷举搜索,而是在搜索过程中通过限界,可以中途停止对某些不可能得到最优解的子空间进一步搜索(类似于人工智能中的剪枝),故它比穷举法效率更高。 [2] 

回溯法

回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

其基本思想是,在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。 [2] 


二、链表

三、栈

1. 什么是栈

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

2. 括号验证

leetcode地址:https://leetcode-cn.com/problems/valid-parentheses/

难度:简单

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

示例 1:

输入:s = "()"
输出:true


示例 2:

输入:s = "()[]{}"
输出:true


示例 3:

输入:s = "(]"
输出:false

20.gif

解题思路:

遍历字符串中的所有字符
  • 1 , 如 果 遇 到 了 左 括 号 , 就 把 对 应 的 右 括 号 压 栈 ( 比 如 遇 到 了 字 符 '(' , 就 把 字 符 ')' 压 栈)。
  • 2,如果遇到了右括号
    • 1)查看栈是否为空,如果为空,说明不能构成有效的括号,直接返回fal se。
    • 2)如果栈不为空,栈顶元素出栈,然后判断出栈的这个元素是否等于这个右括号, 如 果 不 等 于 , 说 明 不 匹 配 , 直 接 返 回 fal se 。 如 果 匹 配 , 就 继 续 判 断 字 符 串 的 下 一 个 字 符。
    • 3,最后如果栈为空,说明是完全匹配,是有效的括号,否则如果栈不为空,说明不完全 匹配,不是有效的括号

Java代码1:

public class ValidParentheses {
    public static void main(String[] args) {
        String s = "{([]())}";
        System.out.println("括号:" + s + ",是否是有效括号:" + isValid(s));
    }

    public static boolean isValid(String s) {
        Stack<Character> characterStack = new Stack<Character>();
        for (char c : s.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                characterStack.push(c);
            }
            if (c == ')') {
                if (characterStack.isEmpty() || '(' != characterStack.pop()) {
                    return false;
                }
            }
            if (c == ']') {
                if (characterStack.isEmpty() || '[' != characterStack.pop()) {
                    return false;
                }
            }
            if (c == '}') {
                if (characterStack.isEmpty() || '{' != characterStack.pop()) {
                    return false;
                }
            }
        }
        return characterStack.isEmpty();
    }

}

java代码2

public class ValidParentheses {
    public static void main(String[] args) {
        String s = "{([]())}";
        System.out.println("括号:" + s + ",是否是有效括号:" + isValid(s));
    }

    public static boolean isValid(String s) {
        if(s == null || s.length() ==0){
            return false;
        }
        Stack<Character> stack = new Stack<>();

        for(char c : s.toCharArray()){
            if(c == '(') {
                stack.push(')');
            } else if(c =='['){
                stack.push(']');
            } else if(c == '{'){
                stack.push('}');
            } else {
                if(stack.isEmpty() ){
                    return false;
                }
                if(stack.pop() != c){
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }

}

3. 最小栈

leetcode地址https://leetcode-cn.com/problems/min-stack/

描述:

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。

解题思路:

当压栈的值小于栈中最小值时,先把最小值入栈,然后再把
需要压栈的值入栈,最后再更新栈中最小值。如果压栈的值大于栈中最小值的时候,直
接压栈

155.gif

Java代码

public class MinStack {

    static Stack<Integer> stack = new Stack<Integer>();
    static Stack<Integer> minStack = new Stack<Integer>();

    public static void main(String[] args) {
        int[] numb = {1, 2, 1, -5, 6, 4};
        for (int i = 0; i < numb.length; i++) {
            push(numb[i]);
        }
        System.out.println("栈的内容是  :" + stack);
        System.out.println("小栈的内容是:" + minStack);
        System.out.println("最小值getMin():" + getMin());
        System.out.println("top():" + top());
        System.out.println("栈的内容是:" + stack);
        System.out.println("小栈的内容是:" + minStack);
        System.out.println("最小值getMin():" + getMin());
    }

    //peek 不改变栈的值(不删除栈顶的值),pop会把栈顶的值删除
    public static void push(int val) {
        stack.push(val);
        if (minStack.isEmpty()) {
            minStack.push(val);
        } else {
            minStack.push(Math.min(val, minStack.peek()));
        }
    }

    public static void pop() {
        minStack.pop();
        stack.pop();
    }

    public static int top() {
        stack.peek();
        return stack.peek();
    }

    public static int getMin() {
        return minStack.peek();
    }
}

4.区间最大值

四、队列

定义:

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

快手面试题-推荐结果打散

需求:

代码:

public class KuaiShouRecommendenResult {
    //内容:v0,v1,v2,p3,p4,p5,v6,p7,v8,p9
    //要求推荐内容,多个视频+一个图片,视频数最多不多于给定最大值
    public static void main(String[] args) {
        List<String> picAndVideoList = new ArrayList<>();
        picAndVideoList.add("v0");
        picAndVideoList.add("v1");
        picAndVideoList.add("v2");
        picAndVideoList.add("p3");
        picAndVideoList.add("p4");
        picAndVideoList.add("p5");
        picAndVideoList.add("v6");
        picAndVideoList.add("p7");
        picAndVideoList.add("v8");
        picAndVideoList.add("v9");
        List<String> result = new KuaiShouRecommendenResult().getRecommendenResult(picAndVideoList, 2);
        for (String r: result ) {
            System.out.println(r);
        }
    }

    public List<String> getRecommendenResult(List<String> picAndVideo, int maxInterval) {
        List<String> result = new ArrayList<>();
        if (picAndVideo == null || picAndVideo.size() == 0) {
            return result;
        }
        Queue<String> videoQueue = new LinkedList<>();
        Queue<String> picQueue = new LinkedList<>();
        boolean firstPic = false;
        int index = 0;
        int picAndVideoSize = picAndVideo.size();
        //不是图片,则把视频加到返回结果里,如果遇到图片则终止,只第一次加载前面的v
        while (!firstPic && index < picAndVideo.size()) {
            if (isVideo(picAndVideo.get(index))) {
                result.add(index, picAndVideo.get(index));
                index++;
            } else {
                firstPic = true;
            }
        }
        //此时v0,v1,v2已定加载到返回结果里,到了p3位置,此时index=3,小于总数量10,则把剩余的图片和视频保存到相应的队列里
        while (index < picAndVideoSize) {
            if (isVideo(picAndVideo.get(index))) {
                videoQueue.add(picAndVideo.get(index));
            } else {
                picQueue.add(picAndVideo.get(index));
            }
            index++;
        }
        //当前队列有几个元素,目前是3,v0,v1,v2
        int currentSize = result.size();
        //如果此时视频和图片都不为null,如果返回结果里的视频数大于给定数,则加一张图片,重新计数
        while (!videoQueue.isEmpty() && !picQueue.isEmpty()) {
            if (currentSize >= maxInterval) {
                result.add(picQueue.poll());
                currentSize = 0;
            } else {
                result.add(videoQueue.poll());
                currentSize++;
            }
        }
        //如果还有视频,则继续返回
        while (!videoQueue.isEmpty()) {
            result.add(videoQueue.poll());
        }
        //如果视频数大于给定数,且图片不为空,则再加一个图片
        if (currentSize >= maxInterval && !picQueue.isEmpty()) {
            result.add(picQueue.poll());
        }

        return result;
    }

    public boolean isVideo(String clip) {
        if (clip.indexOf("v") != -1) {
            return true;
        }
        return false;

    }


}

五、二分查找

总结

对于不是特别聪明,平时又用不到算法的人来说,算法只有多写,多思考,平时多用这一条路。避免临时抱佛脚,浪费时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值