GithubHelp home page GithubHelp logo

algorithm005-class02 / algorithm005-class02 Goto Github PK

View Code? Open in Web Editor NEW
35.0 4.0 146.0 67.16 MB

Java 49.65% JavaScript 6.44% Python 22.69% C++ 7.49% C 1.52% Go 8.91% PHP 2.21% Shell 0.01% Kotlin 0.55% Objective-C 0.43% Swift 0.12% Hack 0.01%

algorithm005-class02's Introduction

TEST

极客大学「算法训练营第五期 - 2 班」作业提交仓库

请大家通过该链接查看讲师课件并进行下载:链接:https://pan.baidu.com/s/1IU8sf5XQI1ptmBKE_YjkCw 密码:e7ws

仓库目录结构说明

  1. Week_01/ 代表第一周作业提交目录,以此类推。
  2. Week_01/id_G20190343020025代表学号为 G20190343020025 的学员第一周的作业提交目录,以此类推。
  3. 每个目录下面均有一个 NOTE.md 文档,你可以将自己当周的学习心得以及做题过程中的思考记录在该文档中(该项不属于作业内容,是可选项)。

作业提交规则

算法题作业的提交

  1. 先将本仓库 fork 到自己的 GitHub 账号下。
  2. fork 后的仓库 clone 到本地,然后在本地新建、修改自己的代码作业文件,注意: 仅允许在和自己学号对应的目录下新建或修改自己的代码作业。作业完成后,将相关代码 push 到自己的 GitHub 远程仓库。
  3. 提交 Pull Request 给本仓库,Pull 作业时,必须备注自己的学号(最后4位)和提交第几周的作业,如0025_Week 02,是指学号尾号为0025的学员提交的第二周的算法题作业。
  4. 代码文件命名规则:**LeetCode_题目序号_学号(最后4位),比如学号为 G20190343020025 的学员完成 LeetCode_33_108 的第 2 题 后,请将代码文件名保存为 LeetCode_2_0025.py (假设你使用的是 Python 语言)。
  5. 务必按照Pull Request的备注形式和作业文件的命名进行提交,班主任会严格按照命名形式统计大家的作业。

学习总结的提交

  1. 除代码作业外,我们要求学员每周提交一篇当周的学习感想,直接发一个独立的 issue 即可,issue 标题格式为【学号(最后4位)_周】总结题目】。例如【0025_week1】二叉树的更多理解是指学号尾号0025的学员提交的第一周的学习总结。可参考:#1

Review 与点评

  1. 我们要求学员每周至少 Review 并点评其他 5 位同学的作业,点评直接回复在代码作业或学习总结下面都可。

注意事项

  1. 作业公布地址: 我们会在置顶的 issue 中公布当周的算法练习题以及其他注意事项。
  2. 如果对 Git 和 GitHub 不太了解,请参考 Git 官方文档 或者极客时间的《玩转 Git 三剑客》视频课程。从来没用过github的学员看这里的git_quick_guide,或许会对你有帮助奥。https://github.com/algorithm004-01/algorithm004-01/tree/master/Utils

algorithm005-class02's People

Contributors

alloevil avatar bigwillc avatar bingliuys avatar congve1 avatar dynamicfire avatar gongziyu4424 avatar jiangjiang77 avatar jsyt avatar leecj avatar llkongl avatar masonnn avatar maxzxc0110 avatar mdgsf avatar melody-li avatar miserydx avatar momo5566 avatar monroehe avatar pistoolster avatar qilaidi avatar rateyu avatar redhare-ma avatar shall6tear avatar shrekboss avatar togodevlop avatar v2gt avatar windbruce avatar xiaosimaqian avatar xiejnws1 avatar yindongfang avatar zhangyr123 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

algorithm005-class02's Issues

【0133_week0】算法和数据结构总览和刷题方法的学习感想

整体思维导图

做脑图的时候发现自己对于数据结构和算法没有一个整体的认识,基于自己的理解画了几个分支,希望接下来一一补充,等课程结束后再来和现在的上传作业作对对比。

刷题

  • 唯一的诀窍就是反复刷题,一次两次根本不够!
  • 还有一个感触就是要把自己当成一个职业选手看待,对自己有足够搞得要求

【0193_Week 01】学习总结

改写Deque的代码
Deque deque = new LinkedList();

deque.addLast("a")
deque.addLast("b")
deque.addLast("c")
System.out.println(deque);

String str = deque.peekFirst();
System.out.println(str);
System.out.println(deque);

while(deque.size()>0){
System.out.println(deque.removeFirst())
}
System.out.println(deque);

Stack源码分析
push:将元素压入栈顶。
pop:弹出栈顶元素(返回栈顶元素,并删除)。
peek:取栈顶元素(不删除)。
empty:判断栈是否为空。
search:搜索一个元素是否在栈里,并返回其到栈顶的距离。

PriorityQueue源码分析
add()和offer()
add(E e)和offer(E e)的语义相同,都是向优先队列中插入元素,只是Queue接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回false。对于PriorityQueue这两个方法其实没什么差别。

element()和peek()
element()和peek()的语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小的那个元素,二者唯一的区别是当方法失败时前者抛出异常,后者返回null。根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。

remove()和poll()
remove()和poll()方法的语义也完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回null。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。

学习总结:
1.边学边练;
2.多问、多思考
3.沉下心不要浮躁,不断沉淀

[0251_week1] 学习总结

学习总结

学习方法论总结

精通一个领域

  • Chunk it up 切碎知识点
    • 庖丁解牛
    • 脉络连接
  • Deliberate Practicing 刻意练习
    • 基本功是区分业余和职业选手的根本,最大的误区 ===> 只刷一遍。
    • 刻意练习 - 过遍数(五毒神掌)
    • 练习缺陷、弱点地方
  • Feedback 反馈
    • 即时反馈
    • 主动型反馈 ===> 高手代码(GitHub, LeetCode)
    • 被动反馈 ===> code review

切题四件套

  • Clarification
  • Possible solutions
    • compare(time/space)
    • optimal(加强)
  • Coding(多写)
  • Test cases

五步刷题法 - 五毒神掌

刷题第一遍

  • 5分钟:读题 + 思考
  • 直接看解法,多解法,比较解法优劣
  • 背诵、默写好的解法

刷题第二遍

  • 马上自己写 ===> LeetCode 提交
  • 多种解法比较、体会 ===> 优化!

刷题第三遍

  • 过了一天后,再重复做题
  • 不同解法的熟练程度 ===> 专项练习

刷题第四遍

  • 过了一周:反复回来练习相同题目

刷题第五遍

  • 面试前一周恢复性训练

感受

最大的感受是改变了之前对刷题的认识误区:

  1. 之前会觉得刷题没什么用,工作中使用也不多。但是却忽略了刷题可以锻炼编程基本功,基本功不扎实会导致后面越走越累,同时可以在leetcode上获取到很多编程的优化思路。
  2. 之前觉得刷题,一定要靠自己想。但,其实答案很难想出来。更重要的是理解解法,并能举一反三。反复练习。

deque改写

public class DequeTest {
    public static void main(String[] args) {
        Deque<String> deque = new LinkedList<String>();

        deque.addFirst("a");
        deque.addFirst("b");
        deque.addFirst("c");
        System.out.println(deque);

        String str = deque.peekFirst();
        System.out.println(str);
        System.out.println(deque);

        while (deque.size() > 0) {
            System.out.println(deque.removeFirst());
        }
        System.out.println(deque);
    }
}

Queue、PriorityQueue源码分析

Throws exception Returns special value
insert add(e) offer(e)
remove remove() poll()
examine element() peek()
  • 底层实现使用了堆,默认为小根堆。
  • 底层存在使用数据代替二叉树,左右节点为2i+1, 2i+2。

【0055_Week 01】学习总结

学习总结

着重理解时间复杂度和空间复杂度,并刻意练习下对算法的时间复杂度和空间复杂度的分析。

时间复杂度

若存在函数f(n), 使得当n趋近于无穷大时,T(n)/f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作 T(n) = O(f(n)),称为O(f(n)), O为算法的渐进时间复杂度。简称为时间复杂度。

因为渐进时间复杂度用大写O来表示,所以也被称作为大O表示法

直白地讲,时间复杂度就是把程序的相对执行时间函数T(n)简化为一个数量级,这个数量级可以是n, n^2, n^3等

时间复杂度推导原则:
1、如果运行时间是常数量级,则用常数1表示
2、只保留时间函数中的最高阶项
3、如果最高阶项存在,则省去最高阶项前面的系数。

空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,用大O表示,记作S(n)=O(f(n))。常见的空间复杂度按照从低到高的顺序,包括O(1)、O(n)、O(n^2)等。其中递归算法的空间复杂度和递归深度成正比。

  • 在写解法的过程中按照“五毒神掌”的方法去做,确实很有效,增加了自己对算法题的理解,也拓展了自己的思路,同时也加强了记忆,不再那么容易忘记,让自己从看见算法题就懵逼的状态,慢慢找到一些解题的感觉。后面还会继续按照这种方法去刷题,跟着课程去走。继续加油!

【0057_Week01】学习总结

Leetcode26题解题思路:
去除重复元素,双指针法,一个指针遍历数组,另一个指针来保存非重复元素。
这里有两种比较方法:
1、遍历元素和最后一个非重复元素比较,如果不相等,就增加一个非重复元素。
2、遍历元素自身前后比较,把前后出现变化的元素加为非重复元素。
这两个比较的基础都是题干说明的有序数组。

Leetcode641题解题思路:
参考国际站的数组方法,这种方法在理解上比较直观,类似循环队列,队满和队空的判断,就是判断头尾指针的位置。
如果front == end,就是空队列。
如果(front+1)%capicaty==end, 就是队满。这里是一个理解盲点,最开始想的时候是队尾+1,因为是队尾和队头相差1个位置,但到底谁在前谁在后。
第一直觉是队尾在前,因为绕1圈后队尾去到前面。但画图分析后,还是队头在前,因为起始点是0,队头是从0加,队尾是从0减,变成大数。所以队头小于队尾。
另外一个点就是,addFirst和addLast,其中一个是先赋值再累加,另一个是先累加再赋值。因为要利用起来0位置点,不然中间就空了,无法将队头和队尾连贯起来。
同样因为赋值后累加的原因,为与队空区分,数组实际大小比容量大1,有1个单位的空间是永远空闲的。

用Deque的新API改写代码:
Dequedeque = new LinkedList();

deque.addFirst("a");
deque.addFirst("b");
deque.addFirst("c");
System.out.println(deque);

String str = deque.peekFirst();
System.out.println(str);
System.out.println(deque);

while(deque.size()>0){
System.out.println(deque.removeFirst());
}
System.out.println(deque);

分析Queue和Priority Queue码:
看了官方的接口文档和类文档,queue简单,但优先队列的函数没看明白哪个函数是利用不同优先级来出栈。

【0291_Week00】学习总结

  1. 绘制了数据结构和算法的脑图,大致了解了本课程需要学习的知识点,目标是:2个月后有底气在简历上写上熟悉常用的数据结构和算法;
  2. 刻意练习的5毒神功学到了,希望通过刷题能够真正内化这些数据结构,虽然说刷题有些功利了,可是为了过面试还是必须要做的啊;
  3. 现在工作中都调API了,可是真正去考虑性能优化或者排查bug的时候,还是很考验内功的,希望通过这次课程能再一次梳理底层的实现,做到深入。

【0279_week1】学习总结

第一部分课后作业,第二部分学习总结’

第 4 课的课后作业:

1.用add first或add last这套新的API改写Deque的代码:

import java.util.Deque;
import java.util.LinkedList;
public class ModifyDeque {
    public static void main(String[] args) {
        Deque<String> deque = new LinkedList<String>();
        deque.addFirst("a");
        deque.addFirst("b");
        deque.addFirst("c");
        System.out.println(deque);
        String str = deque.peekFirst();
        System.out.println(str);
        System.out.println(deque);
        while (deque.size() > 0){
            System.out.println(deque.removeFirst());
        }
        System.out.println(deque);
    }
}

2.分析Queue和Priority Queue的源码:
Queue的源码示例地址:http://fuseyism.com/classpath/doc/java/util/Queue-source.html
首先Queue是一个Interface,Queue队列继承了Collection接口,并扩展了队列相关方法。是一种为了可以优先处理先进入的数据而设计的集合。

boolean add(E e);
在不超出规定容量的情况下可以将指定的元素立刻加入到队列,成功的时候返回success,超出容量限制时返回异常。

boolean offer(E e);
前面跟add()一样,就是与add相比,在容量受限时应该使用这个。

E remove();
检索并删除此队列的首元素,队列为空则抛出异常。

E poll();
检索并删除此队列的首元素,队列为空则抛出null。

E element();
检索但并不删除此队列的首元素,队列为空则抛出异常。

E peek();
检索但并不删除此队列的首元素,队列为空则抛出null。

我的理解就是:数据只能从队尾进来,从队首离开,跟人在实际生活中的排队很像,先进来的人先离开。Queue严格遵循了这个原则,使插队和提早离开变得不可能。

Priority Queue的源码示例地址:http://fuseyism.com/classpath/doc/java/util/PriorityQueue-source.html
PriorityQueue继承于Queue,相比于一般的队列,它的出队的时候可以按照优先级进行出队,PriorityQueue可以根据给定的优先级顺序进行出队。

主要属性:
private static final int DEFAULT_CAPACITY = 11;
默认的容量。

E[] storage;
元素存储的地方。

int used;
数组中的实际元素数量。

Comparator<? super E> comparator;
比较器。

主要方法:
6种构造函数:

public PriorityQueue()
  71:   {
  72:     this(DEFAULT_CAPACITY, null);
  73:   }
  74: 
  75:   public PriorityQueue(Collection<? extends E> c)
  76:   {
  77:     this(Math.max(1, (int) (1.1 * c.size())), null);
  78: 
  79:     // Special case where we can find the comparator to use.
  80:     if (c instanceof SortedSet)
  81:       {
  82:         SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
  83:         this.comparator = (Comparator<? super E>) ss.comparator();
  84:         // We can insert the elements directly, since they are sorted.
  85:         int i = 0;
  86:         for (E val : ss)
  87:           {
  88:             if (val == null)
  89:               throw new NullPointerException();
  90:             storage[i++] = val;
  91:           }
  92:       }
  93:     else if (c instanceof PriorityQueue)
  94:       {
  95:         PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
  96:         this.comparator = (Comparator<? super E>)pq.comparator();
  97:         // We can just copy the contents.
  98:         System.arraycopy(pq.storage, 0, storage, 0, pq.storage.length);
  99:       }
 100: 
 101:     addAll(c);
 102:   }
 103: 
 104:   public PriorityQueue(int cap)
 105:   {
 106:     this(cap, null);
 107:   }
 108: 
 109:   public PriorityQueue(int cap, Comparator<? super E> comp)
 110:   {
 111:     if (cap < 1)
 112:       throw new IllegalArgumentException();      
 113:     this.used = 0;
 114:     this.storage = (E[]) new Object[cap];
 115:     this.comparator = comp;
 116:   }
 117: 
 118:   public PriorityQueue(PriorityQueue<? extends E> c)
 119:   {
 120:     this(Math.max(1, (int) (1.1 * c.size())),
 121:          (Comparator<? super E>)c.comparator());
 122:     // We can just copy the contents.
 123:     System.arraycopy(c.storage, 0, storage, 0, c.storage.length);
 124:   }
 125: 
 126:   public PriorityQueue(SortedSet<? extends E> c)
 127:   {
 128:     this(Math.max(1, (int) (1.1 * c.size())),
 129:          (Comparator<? super E>)c.comparator());
 130:     // We can insert the elements directly, since they are sorted.
 131:     int i = 0;
 132:     for (E val : c)
 133:       {
 134:         if (val == null)
 135:           throw new NullPointerException();
 136:         storage[i++] = val;
 137:       }
 138:   }

清空队列:

 140:   public void clear()
 141:   {
 142:     Arrays.fill(storage, null);
 143:     used = 0;
 144:   }

比较器:

 146:   public Comparator<? super E> comparator()
 147:   {
 148:     return comparator;
 149:   }

迭代器:

 151:   public Iterator<E> iterator()
 152:   {
 153:     return new Iterator<E>()
 154:     {
 155:       int index = -1;
 156:       int count = 0;
 157: 
 158:       public boolean hasNext()
 159:       {
 160:         return count < used;
 161:       }
 162: 
 163:       public E next()
 164:       {
 165:         while (storage[++index] == null)
 166:           ;
 167: 
 168:         ++count;
 169:         return storage[index];
 170:       }
 171: 
 172:       public void remove()
 173:       {
 174:         PriorityQueue.this.remove(index);
 175:     index--;
 176:       }
 177:     };
 178:   }

添加节点:

 180:   public boolean offer(E o)
 181:   {
 182:     if (o == null)
 183:       throw new NullPointerException();
 184: 
 185:     int slot = findSlot(-1);
 186: 
 187:     storage[slot] = o;
 188:     ++used;
 189:     bubbleUp(slot);
 190: 
 191:     return true;
 192:   }

获取优先级队列头结点:

 194:   public E peek()
 195:   {
 196:     return used == 0 ? null : storage[0];
 197:   }

移除并获取优先级队列头节点:

 199:   public E poll()
 200:   {
 201:     if (used == 0)
 202:       return null;
 203:     E result = storage[0];
 204:     remove(0);
 205:     return result;
 206:   }

移除指定元素:

 208:   public boolean remove(Object o)
 209:   {
 210:     if (o != null)
 211:       {
 212:         for (int i = 0; i < storage.length; ++i)
 213:           {
 214:             if (o.equals(storage[i]))
 215:               {
 216:                 remove(i);
 217:                 return true;
 218:               }
 219:           }
 220:       }
 221:     return false;
 222:   }

队列中元素个数:

 224:   public int size()
 225:   {
 226:     return used;
 227:   }

添加所有元素:

 231:   public boolean addAll(Collection<? extends E> c)
 232:   {
 233:     if (c == this)
 234:       throw new IllegalArgumentException();
 235: 
 236:     int newSlot = -1;
 237:     int save = used;
 238:     for (E val : c)
 239:       {
 240:         if (val == null)
 241:           throw new NullPointerException();
 242:         newSlot = findSlot(newSlot);
 243:         storage[newSlot] = val;
 244:         ++used;
 245:         bubbleUp(newSlot);
 246:       }
 247: 
 248:     return save != used;
 249:   }

寻找插槽(没完全理解):

 251:   int findSlot(int start)
 252:   {
 253:     int slot;
 254:     if (used == storage.length)
 255:       {
 256:         resize();
 257:         slot = used;
 258:       }
 259:     else
 260:       {
 261:         for (slot = start + 1; slot < storage.length; ++slot)
 262:           {
 263:             if (storage[slot] == null)
 264:               break;
 265:           }
 266:         // We'll always find a slot.
 267:       }
 268:     return slot;
 269:   }

移除指定序号的元素:

 271:   void remove(int index)
 272:   {
 273:     // Remove the element at INDEX.  We do this by finding the least
 274:     // child and moving it into place, then iterating until we reach
 275:     // the bottom of the tree.
 276:     while (storage[index] != null)
 277:       {
 278:         int child = 2 * index + 1;
 279: 
 280:         // See if we went off the end.
 281:         if (child >= storage.length)
 282:           {
 283:             storage[index] = null;
 284:             break;
 285:           }
 286: 
 287:         // Find which child we want to promote.  If one is not null,
 288:         // we pick it.  If both are null, it doesn't matter, we're
 289:         // about to leave.  If neither is null, pick the lesser.
 290:         if (child + 1 >= storage.length || storage[child + 1] == null)
 291:           {
 292:             // Nothing.
 293:           }
 294:         else if (storage[child] == null
 295:                  || (Collections.compare(storage[child], storage[child + 1],
 296:                                          comparator) > 0))
 297:           ++child;
 298:         storage[index] = storage[child];
 299:         index = child;
 300:       }
 301:     --used;
 302:   }

冒泡排序的函数:

 304:   void bubbleUp(int index)
 305:   {
 306:     // The element at INDEX was inserted into a blank spot.  Now move
 307:     // it up the tree to its natural resting place.
 308:     while (index > 0)
 309:       {
 310:         // This works regardless of whether we're at 2N+1 or 2N+2.
 311:         int parent = (index - 1) / 2;
 312:         if (Collections.compare(storage[parent], storage[index], comparator)
 313:             <= 0)
 314:           {
 315:             // Parent is the same or smaller than this element, so the
 316:             // invariant is preserved.  Note that if the new element
 317:             // is smaller than the parent, then it is necessarily
 318:             // smaller than the parent's other child.
 319:             break;
 320:           }
 321: 
 322:         E temp = storage[index];
 323:         storage[index] = storage[parent];
 324:         storage[parent] = temp;
 325: 
 326:         index = parent;
 327:       }
 328:   }

扩容:

 330:   void resize()
 331:   {
 332:     E[] new_data = (E[]) new Object[2 * storage.length];
 333:     System.arraycopy(storage, 0, new_data, 0, storage.length);
 334:     storage = new_data;
 335:   }
 336: }

学习总结

除了学会关于数组、链表、栈、队列、优先队列等算法与数据结构的知识与方法外,对我最重要的帮助是以下:
1.通过潭超老师的视频和实践,学会了快速查看源代码。
2.克服了看英文文档的恐惧。
3.使用日程提醒软件来帮助自己完成5次练习
4.世上原创的东西很少,都是模仿、归纳、重复,遇到一些自己解决不了的问题不用死磕(以前经常会,导致效率低下),不会的直接学习就好了,当你学得越多,手中的工具武器越多,相反越容易有创造性的东西出现,量变到质变。
5.默写题目只背原理不背细节,不要一句一句背。

🌟 算法训练营(第5期)第一周作业

要求

  1. 每周从覃超老师布置的题目中,至少完成并提交两道算法题
  2. 围绕每周重点学习的算法知识点,撰写一篇有观点和思考的技术文章或总结,切忌流水账。
  3. 每周需要 review 并点评至少 5 位同学的代码作业或学习总结

作业提交 Deadline

2019年12月15日 23:59 (以当地时间为准)
未按时提交作业,会在个人作业总分 -3 分

本周作业概述

本周需要完成学习的视频内容:

  • 第 3 课 | 数组、链表、跳表
  • 第 4 课 | 栈、队列、优先队列、双端队列

以上两课视频后,覃超老师都给出了大家可以练手的算法题。

其中,第 4 课的课后作业(第2节视频后)还包括:

  • 用add first或add last这套新的API改写Deque的代码
  • 分析Queue和Priority Queue的源码

请大家将此部分作业,放在本周学习总结中一并提交

本周算法习题库:

第三课课后习题:

https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
https://leetcode-cn.com/problems/rotate-array/
https://leetcode-cn.com/problems/merge-two-sorted-lists/
https://leetcode-cn.com/problems/merge-sorted-array/
https://leetcode-cn.com/problems/two-sum/
https://leetcode-cn.com/problems/move-zeroes/
https://leetcode-cn.com/problems/plus-one/

第四课课后习题:

https://leetcode.com/problems/design-circular-deque
https://leetcode.com/problems/trapping-rain-water/
用add first或add last这套新的API改写Deque的代码
分析Queue和Priority Queue的源码

改写代码和分析源码这两项作业,需要在你的本周学习总结中一并提交。

【0229_week1】学习总结

21

归并排序的思路:长度没有限定,两个指针一直遍历到结尾,终止条件:某一个长度为零(next为None)

  1. 没考虑到 [] 空链表
  2. 涉及比较数字大小操作的时候必须用 .val,但是判断是否存在值(None)可以直接用节点
  3. 弱点:对于链表的指针的引用会不确定
  4. 参考简洁写法之后运行时间并没有增加(思路一致)
  5. 在新建 ListNode 的时候最开始我直接用了某个节点,导致返回的值都不对,看起来很奇怪输出的是有节点的数组,后来发现应该用 .val 新建

26

参考解法里面,需要注意最终返回的长度是根据 i 指针来定的。

66

  • 边界点:进一位的处理,进一位之后已经到头了的处理
  • 延伸:这里因为只是加一,所以每次的进一位的数字只会是1,但如果是两个数字相加,就不会是这样了,就要考虑相加之后,这时候大概是应该取商
  • case: [9] 忘记移位下标 i 了
  • 发现了 return 的好处,是在 break 循环的同时还可以返回值

88

  • 用冒号操作时候,[l, r] 后面是开区间
  • 边界情况:p1 一开始就负数,进不去处理,就需要直接拼,但是拼的条件,是 p2 还需要拼的时候(p2 >= 0),数没有走完

42

  • 思路:从两端往中间遍历,哪边大就处理哪边的墙,如果小于本侧最大值,就可以积攒雨水。

[0167_Week01] 一周学习总结与简单双指针理解

学习方法总结

如何刻意练习

  • 刻意练习——过遍数(五毒神掌)
    • 切题四件套
      • Clarification:多读题,与面试官交流,确保自己对题目的理解是正确的。
      • Possible solutions:
        • compare(time/space):想所有可能的解题方法,每个方法过一遍,比较时间空间复杂度。
        • optimal(加强):找出最优解发。
      • Coding(多写):开始写代码。
      • Test cases:测试样例,多个测试用例。
    • 五遍刷题法
      • 第一遍:
        • 5~10分钟(读题+思考)。
        • 没有思路——>直接看解法。注意:多解法,比较解法优劣。
        • 背诵、默写好的解法。
      • 第二遍:
        • 马上自己写——>leetCode 提交。
        • 多种解法比较、体会——>优化!
      • 第三遍:
        • 过了一天后,再重复做题。
        • 不同解法的熟练程度——>专项练习(对自己不是特别熟悉的题目进行专项训练)。
      • 第四遍:
        • 过了一周之后,反复回来练习相同题目。
        • 对于自己不熟悉的题目进行专项练习。
      • 第五遍:
        • 有面试——>面试前一周恢复性训练(之前的题目重新做一遍)。

先开拖拉机再开法拉利

  • 首先尝试暴力求解,不要怕时间空间复杂度高,使用暴力求解能够解决问题对自己也是一种鼓励,因为有些题目本人根本不知道该怎么求解,根本没有想法。
  • 其次尝试其他求解方法,这时就去leetCode国际站去寻找精辟的解法,多记录几种解法,思考求解方法的精妙所在。

自顶向下的编程方式

  • 思考高层次(主干)的逻辑。
  • 处理细节逻辑。

双指针的使用

leetCode上我做过的可以使用双指针的题目

盛水最多的容器
三数之和
删除排序数组中的重复项
合并两个有序数组
验证回文串
环形链表
移动零

两种双指针

  • 快慢指针
    • 举例:环形链表
    • 两种情况:
      • 无环:快指针比慢指针优先到达链表尾端,完成遍历
      • 有环:快指针追上慢指针,证明环形的存在
class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        if (head == null) {
            return false;
        }
        while (fast.next !=null && fast.next.next !=null) {
            slow = slow.next;
            fast =fast.next.next;
            if (slow == fast) {
                return true;
            }
        }
        return false;
    }
}
  • 对撞指针
    • 举例:回文字符串
    • 设置首指针指向字符串的第一个字符
    • 设置尾指针指向字符串的最后一个字符
    • 首尾指针同时遍历,向中间判断
    • 得出结果
class Solution {
    public boolean isPalindrome(String s) {
        int start = 0;
        int end = s.length()-1;
        while (start <= end) {
            while (start <= end && !Character.isLetterOrDigit(s.charAt(start))) {
                start++;
            }
            while (start <= end && !Character.isLetterOrDigit(s.charAt(end))) {
                end--;
            }
            if (start <= end && Character.toLowerCase(s.charAt(start))!=Character.toLowerCase(s.charAt(end))) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }
}

双指针总结

  • 首尾指针确定首尾元素,遍历中间元素,寻找最优解
  • 快慢指针常用于有环的情况,遍历确定环的存在
  • 这位老哥留下的链接非常好,先mark后看
    【0109_week1】关于双指针法的学习

课后作业

  • 用 add first 或 add last 这套新的 API 改写 Deque 的代码
Deque<String> deque = new LinkedList<>();
        deque.addFirst("a");
        deque.addFirst("b");
        deque.addFirst("c");
        System.out.println(deque);

        String str = deque.peekFirst();
        System.out.println(str);
        System.out.println(deque);
        while (deque.size() > 0) {
            System.out.println(deque.pop());
        }
        System.out.println(deque);
  • 分析 Queue 和 Priority Queue 的源码
    • Queue为接口,PriorityQueue为类,priorityQueue实现了Queue接口
    • 其中PriorityQueue实现了Queue的add方法,其实质
public boolean add(E e) {
        return offer(e);
    }
public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        siftUp(i, e);
        size = i + 1;
        return true;
    }
  • PriorityQueue的comparator()是对于元素排序的比较器,用于确定优先级

【0043_week1】学习总结

数组
1、一种线性数据结构,用一组连续的内存空间存储数据。
2、插入、删除操作时间复杂度是O(n),尾部操作是O(1);根据下标随机访问的时间复杂度是O(1)。

链表
1、不需要连续内存空间。
2、插入、删除操作时间复杂度是O(1),随机访问时间复杂度O(n).
3、链表分为单链表、双向链表、循环链表、双向循环链表。

栈与队列
1、操作受限的线性数据结构,栈是后进先出,先进后出;队列是先进先出
2、可用数组或链表实现。

学习方法:
1、最多思考十五分钟,不会,看排名前三题解,去国际站看解法(url去掉-cn);
2、自顶向下编程,先写出大的思路,再写细节。
3、刷至少五遍(一天后、一周后,一个月后),总之多刷没坏处。
4、所有的算法最后就是条件判断、循环、递归的写法。关键在于找到算法重复点。
5、通过提升维度及空间换时间的方式降低时间复杂度。
6、多学习开源的工业级源代码。

[0235_Week01] 学习总结

学习笔记

关于对数组、链表、栈、队列的一些总结与反思

  • 数组、链表、栈、队列都是一维数据结构。其容量的增长只能往一个方向上增长
  • 对于跳表这一数据结构,可以看做是二维数据结构,因为它可以往两个维度上扩展数据。第一个维度就是数据项,第二个维度
  • 将数据结构以维度进行分类,感觉也是非常的巧妙
  • 栈的应用:系统栈等
  • 队列的应用:滑动窗口,消息队列等

学习总结

  • 不要死磕
  • 学习别人的长处,代码风格,解题思路,融为己用

代码改写

Deque<String> deque = new LinkedList<String>();
deque.addFirst("a"); 
deque.addFirst("b"); 
deque.addFirst("c"); 
System.out.println(deque);
String str = deque.peekFirst(); 
System.out.println(str); 
System.out.println(deque);
while (deque.size() > 0) {
System.out.println(deque.removeFirst()); 
}
System.out.println(deque);

Queue 源码分析

  • uml继承结构图 Queue----extends>Collection----extends>Iterable
  • Queue 是一个接口,包含了 add(), offer(), remove(), poll(), element(), peek()六种方法,以及 Collection, Iterable 中的所有接口
  • add()方法将指定元素入队,如果空间不足会抛IllegalStateException异常
  • offer()方法与 add 方法类似,向队列中添加元素,如果队列空间有限,则推荐使用 add()方法
  • remove()方法,删除队列的队首元素,如果队列为空,则抛异常
  • poll()方法,删除队列的队首元素,如果队列为空,则返回 null
  • element()方法,返回队首元素,如果队列为空则抛异常
  • peek()方法,返回队首元素,如果队列为空返回 null

PriorityQueue 源码分析

  • uml 继承图
    • image

    • 内部是采用堆作为优先队列的底层数据结构

    • add 时会 siftup(), remove()时会 siftdown() 保证其优先级

    • 有个疑问,超哥讲的priority queue 的插入操作的时间复杂度是 O(1)是基于什么考虑的,如果底层数据结构是堆,那么它的插入操作的时间复杂度不应为 O(1)吧。

    • PS:对 java 不是很熟,源码分析,只能是简单的看下实现了

【0231_week1】爬楼梯(斐波那契数列)的更多解法及**总结

问题

最早起源于兔子问题

假设:一对成年兔子每年生一对小兔,小兔一年后成年。

提问:一开始有一对小兔,n年后共有多少只兔子

兔子的个数就是一个斐波那契数列

典型的斐波那契数列是:

0 1 1 2 3 5 8 13 21 ....

还有一些例子,如上台阶,每次只能上一级或两级,也是一个斐波那契数列

斐波那契解法

斐波拉契数列形如

f(n) = f(n-1) + f(n-2)

常见的解法有:

  1. 直接递归,时间复杂度为O(2^N)(主定理,或者枚举计算,等比求和),空间复杂度是O(n),递归调用栈的空间

  2. 递归加记忆化,把每个f(n)保存起来,如果计算过就不计算了,空间换时间的思维,时间复杂度为O(N),空间复杂度O(N)

  3. 递推,时间复杂度为O(N),空间复杂度O(1)

  4. 通项公式法,即是求解power(a, n),时间复杂度O(lgN),空间复杂度O(1)

屏幕快照 2019-07-23 上午9 28 08

斐波那契数列通项公式:

f(n) = a1(b1)^n + a2(b2)^n

其中a1, b1, a2, b2四个数字都是常数。
通项公式的计算,并不能O(1)得到,而是一个a^n,即power(a, n)的求解过程。

另外,由于是浮点运算,受限于浮点运算的精度问题,需要做一个取整才能得到整数,计算机的浮点运算还是比较耗时的。

  1. 矩阵相乘法

    winter老师介绍的办法是借助线性代数的矩阵运算,构造斐波那契数列的通项公式的形态,用矩阵相乘的办法来求解,把斐波那契数列转换成矩阵的幂运算,达到O(lgN)

  2. 查表,空间换时间的极致,时间复杂度O(1),空间复杂度(嗯,很大,不知道怎么表示?)

      static final int[] fibs = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040};
      public int fib(int N) { return fibs[N]; }

power(a, n)的求解

Leetcode50

  • 第一种解法:不断的乘以自己,时间复杂度O(N)
  • 第二种解法:递归分治,时间复杂度O(lgN)
  • 第三种解法:通过N的二进制位,迭代分治,时间复杂度O(lgN)

矩阵相乘法

通过使用矩阵乘法和矩阵幂运算来实现时间复杂度O(lgN),空间复杂度为O(1)

目标就是a,b变为b,a+b。

最终的结果用矩阵来表示就是: 屏幕快照 2019-07-24 上午10.40.52.png

下面是用go来实现

func fib(N int) int {
	if N == 0 || N == 1 {
		return N
	}
	// first 是第0个和第1个元素
	first := [][2]int{[2]int{0, 1}, [2]int{0, 0}}
	// temp为系数
	temp := [][2]int{[2]int{0, 1}, [2]int{1, 1}}
	res := matrix22_pow(temp, N - 1)
	return matrix22_mul(first, res)[0][1]
}

func matrix22_pow(x [][2]int, n int) [][2]int {
	r := x
	res := [][2]int{[2]int{1, 0}, [2]int{0, 1}}
	for n != 0 {
		if n & 1 == 1 {
			// 最低二进制位为1
			res = matrix22_mul(res, r)
		}
		// 2维矩阵相乘
		r = matrix22_mul(r, r)
		n >>= 1
	}
	return res
}

func matrix22_mul(x, y [][2]int) [][2]int {
	temp := make([][2]int, 2)
	temp[0][0] = x[0][0] * y[0][0] + x[0][1] * y[0][1]
	temp[0][1] = x[0][0] * y[0][1] + x[0][1] * y[1][1]
	temp[1][0] = x[1][0] * y[0][0] + x[1][1] * y[0][1]
	temp[1][1] = x[1][0] * y[1][0] + x[1][1] * y[1][1]
	return temp
}

**总结

  • 空间换时间
  • 找重复性,最近重复子问题,计算机被设计之初就是用switch,iteration,recursion等来做重复性的工作
  • 其他基础数学也很重要,高等数学,组合数学,线性代数等

参考

极客时间 winter老师的讲解

架构师之路—别再问我斐波那契数列了

课后作业

在java 11 下进行测试

改写代码以及测试用的代码

import java.util.*;

public class Main {
    public static void main(String[] args) {
        System.out.println(8 >>> 1);
        Deque<String> deque = new LinkedList<String>();
        deque.addLast("a");
        deque.addLast("b");
        deque.addLast("c");
        System.out.println(deque);

        String str = deque.peekFirst();
        System.out.println(str);
        System.out.println(deque);
        while (deque.size() > 0) {
            System.out.println(deque.removeFirst());
        }
        System.out.println(deque);

        Queue<Integer> priorityQueue= new PriorityQueue<>();
        Queue<Integer> priorityQueue2= new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        priorityQueue.add(5);
        priorityQueue.add(2);
        priorityQueue.add(1);
        priorityQueue.add(6);
        priorityQueue.add(8);
        priorityQueue.add(3);
        priorityQueue.add(9);


        System.out.println(priorityQueue.peek());
        System.out.println(priorityQueue.remove());
        System.out.println(priorityQueue.remove(3));
        System.out.println(priorityQueue.remove());
    }
}

java优先队列的实现

java中采用的是二叉堆来实现

  • Insert:O(lgN)
  • Delete min/max:O(lgN)
    • remove(Object o) :需要找到索引,O(N)
  • Find min/max:O(1)

用数组来实现heap

Java底层使用数组来存储元素

 Object[] queue; // non-private to simplify nested class access

屏幕快照 2019-12-12 下午11 26 35

用数组array来实现这个树,[1, 2, 3, 4, 5, 6, 7]

  • a[0],表示根
  • 若某个节点的数组索引为i,则其左,右孩子的索引为(2i+1, 2i+2),其父节点的索引为((i-1)/2)

初始化

如果没有指定容量,默认的初始化容量是11;如果指定了容量,使用容量进行初始化,如果指定了比较器,使用指定的比较器进行初始化

add(e)和offer(e)的实现

add直接调用了offer,如果底层的数组queue已经到达最大容量了,调用grow对其进行扩容,如果旧的容量大小小于64,加倍其容量,否则扩容50%,然后新创建一个数组,将旧数组的元素拷贝过去。

     public boolean add(E e) {
        return offer(e);
    }
     public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        siftUp(i, e);
        size = i + 1;
        return true;
    }
    // 扩容		
    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }

如果没有指定比较器,默认实现是小顶堆,根元素是最小值。siftUp函数负责对优先队列进行维护,底层就是维护min heap

   // 维护heap
   private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x, queue, comparator);
        else
            siftUpComparable(k, x, queue);
    }
   private static <T> void siftUpUsingComparator(
        int k, T x, Object[] es, Comparator<? super T> cmp) {
        while (k > 0) {
          	// 找到父节点索引
          	// 无符号右移,忽略符号位,空位都以0补齐
            int parent = (k - 1) >>> 1;
            Object e = es[parent];
          	// 比较当前值和父节点值,如果x比e大或相等,退出循环
            if (cmp.compare(x, (T) e) >= 0)
                break;
          	// 否则调整父节点,进行下一轮比较
            es[k] = e;
            k = parent;
        }
     	// 最后把x放到合适的位置
        es[k] = x;
    }

remove() 和 poll()

remove是从AbstractQueue继承的,调用了poll。

    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
    public E poll() {
        final Object[] es;
        final E result;
	//  拿到根的值
        if ((result = (E) ((es = queue)[0])) != null) {
            modCount++;
            final int n;
          	// 拿到最后一个元素
            final E x = (E) es[(n = --size)];
          	// 清空最后一个元素
            es[n] = null;
            if (n > 0) {
              	// 从索引0开始调整heap
                final Comparator<? super E> cmp;
                if ((cmp = comparator) == null)
                    siftDownComparable(0, x, es, n);
                else
                    siftDownUsingComparator(0, x, es, n, cmp);
            }
        }
      	// 根即为所求
        return result;
    }
    private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
        // assert n > 0;
        Comparable<? super T> key = (Comparable<? super T>)x;
        int half = n >>> 1;           // loop while a non-leaf
        while (k < half) {
          	// 左孩子索引
            int child = (k << 1) + 1; // assume left child is least
            Object c = es[child];
          	// 右孩子索引
            int right = child + 1;
          	// 找到左右孩子中least
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
                c = es[child = right];
          	// 左右孩子中least和x进行比较
            if (key.compareTo((T) c) <= 0)
                break;
            es[k] = c;
            k = child;
        }
        es[k] = key;
    }

remove(Object o)

   public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            // 如果没有这个元素直接返回
            return false;
        else {
            removeAt(i);
            return true;
        }
    }

    E removeAt(int i) {
        // assert i >= 0 && i < size;
        final Object[] es = queue;
        modCount++;
        int s = --size;
        if (s == i) // removed last element
            es[i] = null;
        else {
            E moved = (E) es[s];
            es[s] = null;
          	// 将最后一个元素,插入到删除的位置。下沉moved直到小于等于child
            siftDown(i, moved);
          	// 如果没有下沉,需要考虑提升moved来调整heap
            if (es[i] == moved) {
              	// 向上提升moved,直到大于等于它的parent
                siftUp(i, moved);
                if (es[i] != moved)
                    return moved;
            }
        }
        return null;
    }

peek()和element()

element调用了peek

    public E peek() {
      	// 直接返回根元素
        return (E) queue[0];
    }

【0035_Week_01】 学习总结

数组

  1. 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。是一种线性表(Linear List)。线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。除了数组,链表、队列、栈等也是线性表结构。

  2. 针遍技巧,一般而言,对于排序的数组可以通过双指针法遍历数组,
    a) 从数组开始和结束位置2端遍历
    b) 定义1个慢指针,1个快指针遍历

链表

  1. 跟数组一样,是非常基础、非常常用的数据结构。不过链表要比数组稍微复杂,从普通的单链表衍生出来好几种链表结构,比如双向链表、循环链表、双向循环链表。和数组相比,链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高
  2. 链表在内存空间地址不是连续分配的,可以更有效利用内存空间,在初始化时候不用指定元素个数大小,插入和删除的时间复杂度为O(1),随机访问元素的时间复杂度为O(n)

栈&队列

  1. 栈和队列是一种访问受限的数据结构

  2. 栈只能在一端操作数据结构,队列在头部删除元素,在队尾添加元素

【0227_week_01】学习总结-刻意练习与模块化思考

《异类》这本书恰好之前我也看过,作者是一万小时专家理论的提出者,任何出类拔

萃的技能都需要长时间(在书里作者给出的概念是一万小时)的刻意练习才有希望达

成,我个人很赞成这样的学习方法,所以在小课里看到老师强调这种学习方法的时候

感觉靠谱,然后才买了课程。

一般我们的知识领域可以分为舒适区,学习区,惊恐区。舒适区就是我们已经掌握了

的内容,学习区是需要我们努一把力才能掌握的内容,惊恐区则是在我们现有的知识

背景下还不能理解的内容。刻意练习必须在学习区里进行才是有效的。比如,如果你

写一万个小时的hello world,你永远也不可能成为编程领域的专家。只有不停的挑

战自己,接受有挑战性的任务,不断的做那些需要垫一下脚才能够到的任务,人在这

个领域的技能才能不断提升。

一个人不停的扩大自己的舒适区,不断向学习区发起进攻,缩小自己的惊恐区域,反

复这个过程,剩下的交给时间,长此以往一个领域的专家就诞生了。

在我看来,所谓的五毒神掌就是刻意练习+艾宾浩斯遗忘曲线(背过单词的同学都知

道这条曲线)的合成体。

我自己有一些工程经验,做过工程的同学可能都会有这样的感觉,当你接到一个新的

需求的时候,脑子里出现的不再是一行行的代码,而是一个个封装好的功能模块,编

程在这个时候就好像是一个砌积木的过程。

老师跟我们强调刷题时,如果5分钟还没有思路马上看题解,我觉得也是类似的思路

。比如你去学微积分,肯定不会有人要求你从0到有推出整个微积分的概念,你肯定

是需要知道一些套路,一些模块才能做题的。一个数学家在碰到一个未知的问题时,

他脑子里肯定不是一条条的数学定理,而是一个个已经高度封装好的模块,这样他才

能更加高效的思考。刷题时不懂就看题解,就是要熟知这些套路,然后才能运用这些

套路,以后才有可能把这些算法知识运用到工程项目上。

非常期待下面的学习,我们都能更多的突破自己的学习区,学习更多算法上的经验和技巧。

【0287_week1】对基础数据结构的重新认识

数组

数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据
随机访问原理:计算机通过内存管理器给数组开辟了内存空间,然后通过寻址公式,找到对应的元素
a[i]_address = base_address + i * data_type_size
数组适合查找,但是查找时间复杂度并不是 O(1) ,根据下标随机访问的时候时间复杂度为 O(1)
数组的插入和删除时间复杂度是 O(n),操作之后,需要将操作位置之后的数据,顺序的移动到指定的位置,在最后插入和删除时间复杂度是 O(1)

链表

链表是线性数据结构,不需要连续的内存空间,需要记录下个结点的地址,因此比数组需要的内存更大。
链表的插入和删除时间复杂度是 O(1)。需要遍历整个链表才能找到要找的元素,所以查找时间复杂度是 O(n)
实际应用:Redis (跳表)

先进后出,后进先出就是栈
举个栗子,桌子上堆的书,放都是从下往上放,取则是从上往下去
栈是一种操作受限的数据结构,只支持入栈和出栈操作
实际应用:浏览器的前进、后退,括号匹配,表达式运算

队列

先进先出,就是队列。
举个栗子,食堂排队打饭,排在前面的先打
队列是一种操作受限的数据结构,只支持入队和出队操作
比栈多了一个指针,需要头指针和尾指针
实际应用:kafka

找最近子问题
栈和队列可以用数组和链表实现

提高效率**
升维,空间换时间(参数跳表原理)

刷题不要只刷一遍

【0039_Week01】学习总结

这周学习了数组,链表,跳表,栈,队列,
双端队列,优先队列。

数组这个头部插入居然也可以做到 O(1) 的
时间复杂度是我之前没有想到的,但是认真
想下,其实和尾部插入是一样的,都是空间
换时间罢了。

链表这个数据结构最大的弱点就是查找,所以
就出现了跳表这种数据结构。这个优化的过程
用到了升维和空间换时间的**。链表相关的
题目理解起来没有太多的逻辑困难,但是代码
写起来却很繁琐,很容易出错,需要多多练习。

栈的那些个题目,没有做过的话,都挺难想到
的,像柱状图中最大的矩形,接雨水,都是很
难自己想出来可以使用栈的。

优先队列这个只是一个接口,底层可以用各种
不同的实现方法。你可以用 heap,bst,treap,
甚至你要用数组来实现都是可以的,就是比较慢。

题目分析:盛水最多的容器
这题的双指针向中间靠拢不是很好理解,我这里提供一个思路。

我们把数组最左边的柱子叫做 a,最右边的柱子叫做 b。

假设 a 的高度小于 b。同时在 a 和 b 之间存在着一根柱子 c。

  • 情况1:c <= a。这种情况 a 和 c 构成的面积一定小于 a 和 b 构成的面积。
  • 情况2:a < c <= b。这种情况 a 和 c 构成的面积也一定小于 a 和 b 构成的面积。
  • 情况3:b < c。这种情况 a 和 c 构成的面积还是一定小于 a 和 b 构成的面积。

上面 3 种情况,大家自己画个图就清楚了。
你会发现无论 c 是多高的。a 和 c 的面积都不可能
超过 a 和 b 的面积,也就是说柱子 a 和 ab 之间
的任意一根柱子都没有必要判断了。

第3课

第4课

【0009_week_01】学习总结

第一课
刻意练习是快速掌握职业技能的最好方法,应用在算法学习这个领域,老师提出了“五毒神掌”和“切题四件套”
关键点

  1. 所有高级数据结构和算法最终的基础都是 循环和递归
  2. 算法的根本就是找到它的重复单元

看老师的谷歌使用操作,明白了想要最好的答案还是得习惯英语表达。

第二课
时空复杂度分析
关键点

  1. 自顶向下的思考方式,抓住主干,总体分析清楚了再完成细节

第三课
数组 链表 跳表
关键点
链表优化成跳表的两个重要**

  1. 升维
  2. 以空间换时间

第四课
栈,队列,优先队列
本周时间不够,我还需要多看看这课

【0017_week1】第一周总结

学习内容

1. 数组 (Array)

数组是基本的线性数据结构,各种语言均有实现,如 Java int[],Python list(),数组的特点是查询速度快,时间复杂度为 O(1),插入删除较慢,时间复杂度为 O(n)。

  • Java 中 ArrayList 也是用数组实现

2. 链表 (LinkedList)

链表的实现一般需要一个节点类,节点类中用 next 指针指向下一个节点,也就是单向链表,双向链表是另外使用 prev 指针指向前驱节点。链表需要额外的空间存储相邻节点指针。特点是增加删除快,只需要修改指针的指向,时间复杂度为 O(1),但是查询较慢,需要从头或尾遍历,时间复杂度为 O(n)。可以增加头尾指针进行简单优化。

  • Java 中 LinkedList 是用双向链表实现

3. 跳表 (SkipList)

跳表是在链表的基础上的优化,采用空间换时间和升维的优化**,增加多级索引,查询的时间复杂度为 O(logn),现实中跳表的增加删除会使得索引间隔并不一定是两步,增加删除需要对索引进行更新,所以维护成本较高。

  • 跳表是redis的一个核心组件,也同时被广泛地运用到了各种缓存的实现

4. 栈

栈的特点是先入后出 FILO,增加删除直接操作栈顶元素,时间复杂度 O(1),查询操作时间复杂度 O(n)。

  • 函数的调用,括号匹配等都可以用栈实现

5. 队列

单向队列:先入先出,队列尾增加元素,队列头删除元素。
双向队列:可以对队列的两端进行操作。

复杂度总结

/ Access Search Insertion Deletion
Array O(1) O(n) O(n) O(n)
Stack O(n) O(n) O(1) O(1)
Queue O(n) O(n) O(1) O(1)
LinkedList O(n) O(n) O(1) O(1)
SkipList O(log(n)) O(log(n)) O(log(n)) O(log(n))

五毒神掌

关于超哥的五毒神掌五遍刷题法,觉得非常实用,自己做完一道算法题之后第二天可能就忘了,所以算法确实需要多次练习,熟练掌握每种算法的套路和**。自己也会在课前先尝试刷一遍题,看题解再写一遍,课后再次自己解题,然后看题解再写一遍。希望下周能坚持把本周的题目再刷一遍,不断巩固强化至熟练掌握常用算法套路。

算法**

通过本周的练习也学到了很多算法的**或者说是套路。

1. 栈

用到栈的题目的有:

括号题目相对容易,42题和84题在用栈解决问题的方法上有一定的相似性。自己第一次并不会,看完84题题解后再来看42题也就想到了栈。155题比较巧妙的用辅助栈。

总之,遇到有最近相关性的题目就可以考虑用栈,最近相关性也就是问题的解决从外向内或者从内向外一层一层(一层的剥开我的心~)就是像洋葱一样。

2. 双指针

很多暴力枚举的方法都可以用双指针巧妙地来解决,在接触算法之前完全没有想到过双指针,从来都是暴力循环,也没有考虑过怎么去优化。
双指针相关题目有:

双指针的用法一般有用快慢指针记录不同索引优化算法,左右指针两端向中间夹逼优化遍历。
11题,15题,42题都可以用左右双指针来实现。自己觉得42题双指针方法非常巧妙,看题解前完全没有思路。141,142题环形链表使用快慢指针解决。

3. 哈希表

由于哈希表的查询获取是O(1),另外可以用object作为key,所以使用哈希表也能大幅优化算法。
相关题目:

4. 递归迭代

递归迭代主要用于解决相似重复性的问题,经典的斐波拉契数列。递归就是函数自己调用自己,需要子问题须与原始问题处理同样的事,且必须有个出口,否则会成为死递归。迭代与普通循环的区别是:迭代时,循环代码中参与运算的变量同时是保存结果的变量,也就是当前保存的结果作为下一次循环计算的初始值。迭代难于理解但效率高,递归易于理解效率低,死递归会造成栈溢出,内存开销大。一般问题两种方法可以替代。
相关题目:

5. 队列

相关题目:

6. 数学或其他巧妙思路

66题按照小学数学方法就可以解决,70题可以利用斐波拉契公式计算得出,189题的翻转法非常巧妙,简单容易理解,之前并没有想到。

总结

本周主要学习了数组,链表,跳表,栈,队列等一维线性数据结构,通过刷题也掌握了对这些数据结构的常用操作和一些基本算法的实现,学习到了很多算法**,比如遇到算法题先从暴力破解法开始思考,由简到难一步步优化,思考问题中存在的相似性和重复性过程,升维和空间换时间的思路等。

【0127_Week01】学习总结

数据结构

  • 一维:
    • 基础:数组 array(string),链表 linked list
    • 高级:栈 stack,队列 queue,双端队列 deque,集合 set,映射 map(hash or map),etc
  • 二维:
    • 基础:树 tree,图 graph
    • 高级:二叉搜索树 binary search tree(red-black tree,AVL),堆 heap,并查集 disjoint set,字典树 Trie,etc
  • 特殊:
    • 位运算 Bitwise,布隆过滤器 BloomFilter
    • LRU Cache

算法

  • if-else,switch —> branch
  • for,while loop —> Iteration
  • 递归 Recursion(Divide & Conquer,Backtrace)
  • }}搜索 Search:深度优先搜索 Depth first search,广度优先搜索 Breadth first search,A*,etc
  • 动态规划 Dynamic Programming
  • 二分查找 Binary Search
  • 贪心 Greedy
  • 数学 Math,几何 Geometry

Big O notation

  • O(1): Constant Complexity 常数复杂度
int n = 1000;
System.out.println("Hey - your input is: " + n);
  • O(log n): Logarithmic Complexity 对数复杂度
for (int i = 1; i < n; i = i * 2) {
    System.out.println("Hey - I'm busy looking at: " + i);
}
  • O(n): Linear Complexity 线性时间复杂度
for (int i = 1; i <= n; i++) {
    System.out.println("Hey - I'm busy looking at: " + i);
}
  • O(n^2): N square Complexity 平⽅
for (int i = 1; i <= n; i++) { 
    for (int j = 1; j <=n; j++) {
        System.out.println("Hey - I'm busy looking at: " + i + " and " + j);
    } 
}
  • O(n^3): N square Complexity ⽴方
  • O(2^n): Exponential Growth 指数
int fib(int n) {
    if (n <= 2) return n;
    return fib(n - 1) + fib(n - 2);
}
  • O(n!): Factorial 阶乘


时间复杂度曲线

时间复杂度曲线

常用解题方法:

嵌套循环:

for (int i = 0; i < nums.length - 1; i++) {
    for (int j = i + 1; j < nums.length; j++) {
        ...
    }
}

双指针(双向夹逼):

for (int i = 0, j = height.length - 1; i < j;) {
    ...
}

【0053_Week 01】学习总结

改写DeQuee代码:

Deque<String> deque = new LinkedList<String>();
deque.addFirst("a");
deque.addFirst("b");
deque.addFirst("c");
System.out.println(deque);
String str = deque.peekFirst();
System.out.println(str);
System.out.println(deque);
while (deque.size() > 0){
    System.out.println(deque.removeFirst());
}
System.out.println(deque);

关于Queue和PriorityQue:

  • Queue在Java中本身是一个Interface,其上一级的Interface有Collection和Iterable。
  • Queue本身定义了几个主要的方法:
1. boolean add​(E e); //向队列中增加元素,若队列已满,则抛出IllegalStateException异常
2. E element(); //获取队首元素(但是元素不会出队),若队列为空则抛出NoSuchElementException异常
3. boolean offer​(E e); //效果类似add,但是如果queue本身对容量有限制时,add()方法只能通过抛出异常返回,而offer()会返回一个false的状态
4. E peek(); //效果类似element(),但是当队列为空时,返回null
5. E poll(); //取出队首元素(元素会被从队列中移除),queue为空时会返回null
6. E remove(); //效果类似poll(),但是queue为空时抛出异常
  • PriorityQue,是继承了AbstractQueue抽象类的类型,在构造函数中允许传入Comparator以用于元素自定义优先级的比较(若Comparator为缺省状态,则以元素默认排序)
    • 优先队列元素不允许为空,也必须为Comparable对象
    • 优先队列通过重写offer()方法,在数据插入时即维护一个优先级的数组对象,达成优先队列的目的。

学习总结:

  • 刷题真的很重要
  • 五遍刷题法是有必要的。现在回去看以前刷过的LeetCode的题目,就有些想不起来当时的想法了,所以还是需要重复巩固,另外可以的话可以写写题解之类的
  • 第一周本身的数组、链表、队列的操作还是比较简单的,有些还是考验**的巧妙性,比如最大容积那题,双指针的解法就需要一定的思考

【0237_week1】学习总结

#+TITLE: 学习笔记

  • 第一周学习总结

** 总结

一维数据结构:

  • 数组、链表
    • 数组,是一块连续的内存空间,可以根据下标快速获取元素值, 而插入、删除元素效率低O(n), 因为会造成内存数据的群移
    • 链表只可以从head, tail指针遍历元素查找元素,但插入、删除效率高,内部非连续存储空间,可以通过next, prev指针的操作快速实现插入、删除
  • stack, queue, deque, priority queue

twoSum, threeSum, FourSum, nSum

  • 复杂度可以做到比暴力枚举少一层循环, 2sumO(n), 3sumO(n^2)
  • 2sum可以哈希表实现
  • 3sum可以先排序,用头尾指针遍历实现,通过操纵指针的跳转,快速实现去重,而不是每次都去set()里查找

双端循环链表:

  • [head, tail), 左闭右开,可以让代码简洁很多,通过浪费一个元素的存储空间,来区分isEmpty()和isFull()
    • isEmpty(): head==tail
    • isFull(): head==(tail+1)%k

双指针的套路:

  • 针对排序元素,一头一尾向中间遍历
  • 快慢指针,判断链表是否有环

** 代码改写

Java环境未配置好, 代码未验证

#+begin_src java
// // old API
// Deque deque = new LinkedList();
// deque.push("a");
// deque.push("b");
// deque.push("c");
// System.out.println(deque);

// String str=deque.peek();
// System.out.println(str);
// System.out.println(deque);

// while (deque.size()>0) {
// System.out.println(deque.pop());
// }
// System.out.println(deque);


// new API
Deque<string> deque = new LinkedList<String>();
deque.addFirst("a");
deque.addFirst("b");
deque.addFirst("c");
System.out.println(deque);

String str=deque.peekFirst();
System.out.println(str);
System.out.println(deque);

while (deque.size()>0) {
System.out.println(deque.removeFirst());
}
System.out.println(deque);

#+end_src

** Queue, Priority Queue 源码分析

来源: openjdk-13-source

*** queue

java中Queue是一个接口,提供以下两类API, 对应插入、删除、获取三种操作。

|         | Throws exception | Returns special value |
|---------+------------------+-----------------------|
| Insert  | add(e)           | offer(e)              |
| Remove  | remove()         | poll()                |
| Examine | element()        | peek()                |

这里主要分析ArrayDeque和LinkedList两个类的实现。

**** ArrayDeque:

底层数据用数组实现,用head, tail两个指针维护队列,实际上是一个双端循环队列(stack + queue):
- head表示deque的头部元素, 对应remove()返回的元素
- tail表示未来*下一个*元素入队的位置

其中:
- head==tail, 表示isEmpty()
- 没有isFull()函数,其中每次addLast, addFirst, add内部,都会检查队列是否满,如果满的话, grow(1), 保证head!=tail

| 函数    | 内部实现                     | Time |
|---------+------------------------------+------|
| add     | addaLast()                   | O(1) |
| remove  | removeFirst() -> pollFirst() | O(1) |
| element | getFirst()                   | O(1) |

**** LinkedList

底层数据用链表实现, 用first, last两个指针表示链表的头尾指针

| 函数    | 内部实现                       | Time |
|---------+--------------------------------+------|
| add     | linkLast()                     | O(1) |
| remove  | removeFirst() -> unlinkFirst() | O(1) |
| element | getFirst() ->                  | O(1) |

*** priority queue

类PriorityQueue实现了Queue接口, 底层数据用最小heap(内部实际是一个数组, 父结点值比子节点值小)实现

| 函数    | 内部实现                        | 复杂度          |
|---------+---------------------------------+-----------------|
| add     | offer() -> siftUp()             | O(logn), 维护堆 |
| remove  | poll()  -> siftDownComparable() | O(logn), 维护堆 |
| element | queue[0]                        | O(1)            |

【0269_week1】算法学习周总结

一、数组(Array)

1. 什么是数组?

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

2. 数组有哪些特点?

1)线性表:最多只有前和后两个方向。

2)数组内的元素是相同的数据类型。

3)连续的内存空间和:「通过下标」随机访问的时间复杂度为 O(1);插入和删除比较低效,可能会引发数组内元素的群移。

4)对插入和删除操作的优化:

  • 插入:c 移动到最后,再将 x 插入 c 原来的位置。

img

  • 删除:a, b, c 全部删除完毕后,后面的元素再进行群移。

img

3. 数组的复杂度分析

image-20191215133745416

二、链表(Linked list)

1. 什么是链表?

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。——wikipedia

常见的链表结构有:单链表、双向链表、循环链表。

2. 链表有哪些特点?

1)和数组一样,链表也是一种线性表数据结构。

2)数组使用连续的内存空间来存储,链表可以将一组零散的内存空间串联起来使用。

3)链表通过指针将零散的内存块串联在一起,内存块称之为「结点」,结点除了存储数据,还需要记录链上下一个结点的地址,称之为后继指针「next」。

4)因为无需保持内存空间的连续性,链表插入和删除的效率很高,时间复杂度为 O(1);也因为如此,无法使用寻址公式查找元素,需要依次遍历。

img

3. 链表的复杂度分析

image-20191215133843397

4. 链表的实际应用场景

LRU cache

三、跳表(Skip list)

1. 什么是跳表?

链表加多级索引的结构,就是跳表。

2. 跳表有哪些特点?

1)相比于链表,跳表的查找效率更高。

3. 跳表的复杂度分析

查询的时间复杂度为 O(logn),空间复杂度为 O(n)。

4. 跳表的实际应用场景

Redis

四、队列(Queue)

1. 什么是队列?

队列,是先进先出(FIFO, First-In-First-Out)的线性表。——wikipidia

其他常见的队列:deque(双端队列)、priority queue(优先队列)

2. 队列有哪些特点?

1)先进先出(FIFO)。

2)队列是一种操作受限的线性数据表结构。

3)通过数组实现的栈,叫做「顺序队列」;用链表来实现的栈,叫做「链式队列」。

4)队列有两个基本操作:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。

3. 队列的复杂度分析

1)正常情况下,入队和出队的时间复杂度都为 O(1);当空间不够时,入队的时间复杂度为 O(n)。

4. 队列的实际应用场景

高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。

五、栈(Stack)

1. 什么是栈?

堆栈(英语:stack)又称为栈或堆叠,是计算机科学中的一种抽象数据类型,只允许在有序的线性数据集合的一端(称为堆栈顶端,英语:top)进行加入数据(英语:push)和移除数据(英语:pop)的运算。——wikipidia

2. 栈有哪些特点?

1)后进者先出(LIFO),先进者后出(FILO)。

2)栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。

3)相比于数组和链表,栈适用于「只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性」的场景。

4)通过数组实现的栈,叫做「顺序栈」;用链表来实现的栈,叫做「链式栈」。

3. 栈的复杂度分析

1)时间复杂度:压栈和弹栈的时间复杂度均为O(1)级别,因为只需更改单个节点的索引即可。

2)空间复杂度:存储数据只需要一个大小为 n 的数组就够了,在入栈、出栈的过程中,只需要一两个临时变量存储空间,所以空间复杂度为 O(1)。对于极个别动态扩容的顺序栈,空间复杂度为 O(n)。

3)注意:这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。因为,这 n 个空间是必须的,无法省掉。所以我们说空间复杂度的时候,是指「除了原本的数据存储空间外,算法运行还需要额外的存储空间」。

4. 栈的实际应用场景

1)浏览器的前进、后退功能

2)函数调用栈

3)表达式求值

4)括号匹配

六、学习感悟

  1. 没有完美的数据结构,Coding 时要根据数据结构的特点进行选择
  2. 读源码可以让你更透彻的了解数据结构的底层**
  3. 升维**:空间换时间(跳表)
  4. 切碎知识点 --> 刻意练习 --> 寻求反馈

【0307_week1】学习总结

1、为啥Redis使用跳表 (Skip List) 而不是使用 Red-Balck
1 skiplist的复杂度和红黑树一样,而且实现起来更简单。
2 在并发环境下skiplist有另外一个优势,红黑树在插入和删除的时候可能需要做一些rebalance的操作,这样的操作可能会涉及到整个树的其他部分,而skiplist的操作显然更加局部性一些,锁需要盯住的节点更少,因此在这样的情况下性能好一些。

【0265_week1】学习总结

一、暴力破解法的学习总结:
来源【三数之和】算法题
1、是对数组/元组/集合之间的转换不熟悉,不清楚列表可以排序,集合可以去重等基础操作,导致一直出现重复答案这个事情;
2、则是对题目理解不透彻,是任意无放回地取3个元素,刚开始理解成有放回,导致答案中出现[0, 0, 0]这种重复元素的情况;
3、在三层循环的时候,无放回,下一层循环应该是从 i + 1 开始!

二、关于链表和递归的思考:
来源【合并两个有序链表】算法题
1、然后判断节点值的大小,小值的留下,再对比小值的下一个节点与大值节点,同理小值节点留下,合并的同时构造有序链表;
2、这里往下对比采用递归的形式,递归就是先逐渐往下推导至初始点,或者说是起始点(已知值),然后再从已知值开始一步步往后赋值,直到当前的N位置,即得到想要的当前递归值。

三、关于双指针算法思路的思考:
来源【接雨水】算法题
1、根据接雨水题意,这里有两个关键点
2、第一是只需左右的柱子群体里各有一个柱子高于当前柱子,即可存储雨水
3、第二是左右的柱子群体中,只需要任意一个柱子较高即可,不管柱子所处的位置
4、通过设立左右两个指针,分别指向左右高柱子,当左柱子小于右柱子,雨水高度取决于左柱,因为这时候右边柱子群是一定可以帮助存储雨水的,不用担心遍历是刚开始还是已经结束,因为已经有比左柱子大的右柱子存在,所以不需要接触到所有右柱子
5、同理,当右柱子小于左柱子,右柱子决定雨水高度(本身柱子很高,那就存不了水),这样两边往中间夹逼,最终遍历结束得到汇总结果,并且是节省了时间,尽量减少算法重复项的出现。

【0357-week1】学习总结

Big O notation

O(1):Constant Complexity 常数复杂度
O(log n):Logarithmic Complexity对数复杂度
O(n):Linear Complexity线性时间复杂度
O(n^2):N square Complexity平方
O(n^3):N cubic Complexity立方
O(2^n):Exponential Growth指数
O(n!):Factorial阶乘

只看最高复杂度的运算

1 move zeros 算法

algorithm method0
第一遍写的时候并没有遵循原数组的原则,而是开辟新数组来存储非0数据,统计0的个数,并补全数组尾部
algorithm method1
看了中文leetCode后,尝试用将所有非0元素前移的方法,初始化索引0,若遇到非0数据,则将该数据与index位置数据进行swap,并且index自增(保证index所在位置及前列都非0),遍历数组后,数组lenth-index的长度(数组末尾处)置为0
algorithm method2
中文leetCode方法,初始化一个指针leftPos,代表新数组当前处理位置,进行数组遍历,若该数不为0,且当前数i index大于leftPos, 则将leftPos位置置为i数据,index位置置为0,否则leftPos自增
algorithm method3 滚雪球法
英文leetCode方法, snowBallSize为雪球大小,遍历数组,若为0,则snowBallSize自增,否则,将该位置数据前移 index-snowBallSize

idea 常用快捷键操作

  • 删除光标右侧
  • win:delete mac:fn+delete
  • 最近编辑文件
  • mac:command+e win:ctrl+e
  • 行头行尾
  • mac:command+left/right win:home/end
  • 光标按单次切分
  • mac:option+left/right win:ctrl+left/right
  • 删除单词
  • mac:option+delete wiin:ctrl+backspace
  • 选中整行
  • mac:shift+command+right win:shift+home/end
  • 修复
  • mac:option+return win:alt+enter

切题四件套

  • classification
  • possible solutions
    capare(time/space)
    optimal
  • coding
  • test cases

五毒神掌

第一遍
5分钟:读+思考
直接看解法:多解法,比较解法优劣
背诵/默写
第二遍
马上自己写->leetCode提交
多种写法比较->优化
第三遍
过了一天,再重复做
不同解法的熟练程度->专项练习
第四遍
过了一周,反复回来练习相同的题目
第五遍
面试前一周恢复性训练

栈和队列的实现及特性
Stack
lifo
queue
fifo
添加删除皆为O(1)
查询O(n)

Deque double ended queue
插入删除皆为O(1)
查询O(n)

如何查询接口信息
google : stack java 12

Priority Queue
插入O(1)
取出O(logn)按照元素优先级取出
底层实现的数据结构较为多样和复杂:heap、bst、treap

【0289_Week 01】学习总结

第一周由于个人原因,将学习任务积压在了周末,实在是压力山大啊。这里总结一下:

  1. 积压学习导致学习效率下降的厉害,特别是刷题部分,容易陷入不耐烦。(头痛)
  2. 实践老师的解题方法,避免浪费时间和挫败感。
  3. 掌握了数组反转、双指针的一些常用套路,非常好用。
  4. 栈、队列、deque、Priority Queue 方面还需要加强

下周计划:

  1. 快速过视频
  2. 尝试每天早上刷题,看题解
  3. 实践五毒神掌

[0161_Week01]学习总结

这周学习的内容数组、链表、跳表、栈、队列、优先队列、双端队列。相比于算法知识点的学习,关于算法的学习方法更值得我们学习。

第一刷题方法:
1.题目看3遍;
2.思考5分钟,列出能想到的所有方法;想不出来直接看答案(国内站+国际站)即可。
3.开始手撸代码,一点都不会的话直接背。
4.分析解法的时间复杂度和空间复杂度。
5.比较各种解法优劣和使用场景。

找重复性
算法的练习不会涉及到业务,所以我们用到的流程就3个:1.if-else;2.for、while、loop;3.recursion(递归).这么一想的话解法主要集中在判断和找重复性。找重复性、找重复性、找重复性就变成解题的关键。

懵逼的时候怎么办?
1.暴力求解
2.分析基本情况,可以使用特殊值枚举几个情况找规律;
3.根据枚举的基本情况找重复性,找最近重复子问题,进而找思路。
4.同时可以考虑升维和空间换时间的方法。

心得
常见的套路需要记忆,日常练习很重要、日常练习很重要、日常练习很重要,就跟练武练基本功,基本的编程技能和常见题型的套路要形成肌肉记忆
1.找到正确的方法
2.将目标拆分成可执行的步骤、小目标
3.刻意练习
4.及时反馈
5.对反馈的问题进行调整和可以练习
6.重复3-5

【0047_week1】关于第一周的学习总结

Java 改造deque

Deque deque = new LinkedList();

deque.addFirst("0");
deque.addLast("1");

System.out.println(deque);

String str = deque.peekFirst();

System.out.println(str);
System.out.println(deque);

while (deque.size() > 0 ){
System.out.println(deque.removeFirst());
}

System.out.println(deque);

Queue源码

在Java中,queue是Interface,有很多种实现方法比如ArrayQueue、LinkedList、ConcurrentLinkedQueue等等
主要包括的方法插入:add(e)、offer(e);查询element()、peek();删除remove()、poll()
每种方法的前者是会在报错时抛出异常比如add(e)在e为null时会抛出NullPointerException ,
后者是会返回boolean

##PriorityQueue
优先队列继承了AbstractQueue,也实现了序列化
PriorityQueue有多个构造函数
image
从图片中可以看到构造函数的描述,有对优先队列指定大小的,也有指定大小同时指定比较器进行排序
多种构造函数可以我们更方便的作用于很多场景当中

优先队列比较关键的是comparator()方法,使用的Comparator接口,返回优先队列的排序方法从而筛选。

在安全性上说PriorityQueue是非线程安全,如果需要线程安全Java中提供了PriorityBlockingQueue接口,主要是通过BlockingQueue来实现的。

【0341_week 01】时间复杂度的上下文.md

本周学习小结

概念区分

算法和数据结构是2个概念,不同数据结构完成相同任务的算法复杂度可能存在差异

本周主要学习的是线性数据结构

数组,链表,栈,队列

算法有时间复杂度和空间复杂度之分

时间复杂度

单单谈论时间复杂度应该是无意义的。应该基于2点讨论

  1. 数据结构是什么
  2. 操作是什么?是访问数组下标,还是查找某个数组元素。

因此对于不同数据结构的不同操作,有不一样的时间复杂度。

双向链表的insert和delete都是O(1)的疑问:插入和删除首先需要查找前一个元素,我觉得应该是O(n)的复杂度

对于数组lookup是O(1)的疑问:如果是访问某个下标,O(1)没问题,如果是查找某个元素,应该也是O(n)

另外对于栈和队列基于底层实现的数据结构的不同,不同的操作应该也是有不同的时间复杂度。

跳表

跳表是本周学习的一大收获,用空间换时间的典范。暂且先记下时间复杂度为logn,空间复杂度为n。理解起来感觉有点吃力

本周存在的问题

刷题量不够,勉强能完成任务。

虽然任务重,但是希望自己能静下心来,真正有所思。

【0173_week1 第一周学习总结】

数据结构
数组 Array
由于具有随机访问的特性,随机查询时间复杂度为O(1)。不过因为内存地址连贯的原因,增删时最坏的情况需要移动其他所有项目腾出位置才行,时间复杂度会比较高,为O(n)。

链表 LinkedList
根据不同链表(单向链表,双向链表,循环列表)的特性,在数据中保存指向下一个、上一个节点地址的信息,因此内存地址可以分散开保存数据,增删时只需修改相应节点地址信息即可,时间复杂度十分稳定,为O(1)。不过正由于只有相邻节点才有可能知道下一个节点的存储地址,因此查询特定节点时需要从链表其中一头遍历到目标节点才可以。因此查询的时间复杂度为O(n)。
常用优化手段:哨兵

跳表 SkipList
跳表属于基于空间换时间的概念来实现链表的数据结构,不只会保存相邻的节点地址信息,还会多保存特定间隔的节点地址信息,以此省略查询目标节点所需的步数。查询的时间复杂度为O(logn)。
但是正因为多保存了特定间隔的节点地址信息们,所以每次增删都需要更新相关节点的数据,因此增删的效率就没有传统链表来的高了。
第一次了解,很经验@!

栈 Stack
具有后进先出的特性,增删操作时间复杂度为O(1)。但是查询的话需要遍历,因此时间复杂度为O(n)。

队列 Queue
具有先进先出的特性,增删操作时间复杂度为O(1)。查询与栈一样需要遍历,因此时间复杂度为O(n)。

双端队列 Deque
既可以在头部操作增删,也可以在尾部操作增删,类似于栈与队列的结合。时间复杂度与栈和队列相同,增删为O(1),查询为O(n)。

PriorityQueue源码分析
add()和offer()
add(E e)和offer(E e)的语义相同,都是向优先队列中插入元素,只是Queue接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回false。对于PriorityQueue这两个方法其实没什么差别。

element()和peek()
element()和peek()的语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小的那个元素,二者唯一的区别是当方法失败时前者抛出异常,后者返回null。根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。

remove()和poll()
remove()和poll()方法的语义也完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回null。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。

【0337_week1】第一周的学习总结

#优先级队列

##过程分析

建堆过程,从array的中间开始到第一个元素,执行siftDown操作。siftDown是自上而下建堆(从哪个位置开始,那么,以这个位置为根的子树将变成堆。)
add(offer)操作,从Array的尾部添加上元素,因为原本已经是建好的堆,我们可以执行siftUp操作,siftUp是自下而上建堆,看起来像个向上冒泡的过程(让满足compare条件的数浮上去)。
删除指定位置i的元素:如果该位置在末尾,直接置为NULL,不是的话,把队尾元素放在待删除位置,做siftDown操作,这样从位置i 开始向下形成了一个堆。由于最后一个节点和待删除节点可能不在一棵子树上,这种情况发生的情况下需要自节该删除节点向上再用siftUp方法捋一遍。

##好用的位操作

//取队列一半,>=half的都没有孩子,<half的有孩子
int half = size >>> 1

//k的左孩子
int leftchildren = (k<<1) + 1

//k的右孩子
int rightchildren = (k<<1) + 2

//k的父母
Int parent = (k-1)>>>1

#关于学习中的道:

  1. 20分钟写不出来不要跟自己过不去,看看答案背过他
  2. 一些优化**注意积累,例如双指针法剪支等
  3. 缓存,做过的东西记下来,空间换时间
  4. 多读JDK源码,有些优秀的高效的写法要记录下来

[0165_Week1] 学习总结

本周总结

  • 本周主要学习了数组,链表,跳表,栈和队列
  • 数组是一段连续的内存空间
  • 链表相对灵活,但是存储指针要占用多余的内存
  • 跳表主要是空间换时间,多层索引,提高链表的查询效率
  • 栈先进后出,队列先进先出,添加删除都是o(1)

个人状态

  • 我也想把本周自己遇到的问题和所犯的错误进行总结。因为除了周一和周末刷了几道题以外,其他的时间几乎没用到刷题上,无毒刷题法也没有用起来,这是一个很严重的错误。希望自己通过总结也能提醒自己,以免恶性循环。
  • 本周个人状态极差。工作瞎忙,学习没有效率。没有把握好刷题节奏。最重要的一点是从心里没有把刷题重视起来。

刷题感悟

  • 第一次刷leetcode题。首先选了11题盛最多水的容器。第一遍写了一个暴力求解法,结果超时了。知道了暴力法的缺点,要根据数据量来定。如果数据量很小,可以用暴力法,如果数据量很大,那就是个灾难。
  • 暴力法试过之后,最优解有大概思路,可是写不出来。只能先去看答案。看完答案题解出来了。这时候犯了一个致命错误。第二天没有重新再来一遍。这也是说个人状态差的症状。从意识到问题到周末结束,没有解决这个问题。
  • 283题移动零,刷完了以后觉得没有问题。然后见识到同组的liliin同学的php数组函数array_filter*操作。这个对我感触很深。解决问题的办法有很多种,并不一定用最原始的方法来解决问题就是最好的。善用各种方法也是很有必要的。
  • 21题合并两个有序列表,思路都会,一做不对。说白了眼高手低。指针的指向问题还是有待提高。

【0011_Week01】学习总结

学习总结

数据结构

  • 本周学习了数组、链表、栈、队列等数据结构。这周听完课后把常用的数据结构的源码过了一遍,学到了很多。由于是前端,工作中对时间复杂度以及空间复杂度的关注不够多,一直在用封装好的高级数据结构,没有关注过底层的实现,以后要多考虑数据结构对程序带来的影响。

学习感受

  • 超哥反复强调不要在一个问题上思考时间过长、钻牛角尖,应该直接看题解然后五毒神掌,感觉非常实用,用这种办法效率提高了很多。
  • 空间换时间:这个**很重要,比如两数之和问题中用哈希表记录已经遍历过的数组值和下标的键值对使得用O(n)的时间复杂度就可以解题,还有第3课上提到的跳表中利用多级索引提高查询效率(牺牲空间和增删效率)。
  • 做题的套路:很多题想破脑袋也想不出非暴力的解法,但在看完题解后恍然大悟。比如数组的问题很多都是双指针解法(移动零、合并有序数组、三数之和),还有环形链表的双指针;柱状图中的最大矩形巧妙地用栈来记录左右边界来辅助计算面积;窗口问题使用双向队列。上面提到的套路需要熟记,做题中要灵活运用。

新的API改写Deque的代码

        Deque<String> deque = new LinkedList<>();
        deque.addFirst("a");
        deque.addFirst("b");
        deque.addFirst("c");
        System.out.println(deque);

        String str = deque.peekFirst();
        System.out.println(str);
        System.out.println(deque);

        while (deque.size() > 0) {
            System.out.println(deque.pollFirst());
        }
        System.out.println(deque);

分析Queue和Priority Queue的源码

实现

优先队列在java中的实现是一个用数组描述的堆(完全二叉树),用数组描述的堆不会浪费多余的空间

关于用数组描述的堆:
  • 每个结点的左孩子为下标i的2倍:left child(i) = i * 2
  • 每个结点的右孩子为下标i的2倍加1:right child(i) = i * 2 + 1
  • 每个结点的父亲结点为下标的二分之一:parent(i) = i / 2,注意这里是整数除,2和3除以2都为1

构造方法

    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        //初始容量
        this.queue = new Object[initialCapacity];
        //比较器,用来定义结构中元素的优先级
        this.comparator = comparator;
    }

元素的添加

    public boolean add(E e) {
        return offer(e);
    }
    
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        //容量不够需要扩容
        if (i >= queue.length)
            grow(i + 1);
        //更新当前记录的元素数量size
        size = i + 1;
        if (i == 0)
        	//第一个元素直接添加
            queue[0] = e;
        else
        	//添加元素时,执行上移操作
            siftUp(i, e);
        return true;
    }
    
    private void siftUp(int k, E x) {
        if (comparator != null)
        	//传入比较器则使用
            siftUpUsingComparator(k, x);
        else
        	//没传入比较器则使用元素实现的Comparable接口
            siftUpComparable(k, x);
    }
    
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
        	//父节点下标
            int parent = (k - 1) >>> 1;
            //交换上浮
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }  

元素的弹出

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        //取出优先级最高的元素
        E result = (E) queue[0];
        //尾部元素置空
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
        	//下沉操作
            siftDown(0, x);
        return result;
    }
    
    private void siftDown(int k, E x) {
    	//比较器的判断,同上移
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
    
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
        	//左孩子下标
            int child = (k << 1) + 1;
            Object c = queue[child];
            //右孩子下标
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                //取优先级较高的孩子
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
            	//如果发现已经父节点小于两个孩子,跳出循环
                break;
            //交换
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

优先队列源码分析体会

由于缺乏对树和堆结构的认知和使用,第一遍看源码时对上移和下沉操作非常困惑,于是搜了一下网上的文章,对数组描述的堆有了认识,再看源码才能读通。希望接下几周能继续补充自己对数据结构的知识盲点。

参考https://blog.csdn.net/weixin_37373020/article/details/93577529

【0109_week1】关于双指针法的学习

本周在解题过程中,多次用到双指针,双指针又可以分为快慢指针和左右指针,分别解决不同问题。因为是第一次系统学习数据结构的和算法,怕自己说不清楚,所以找到一些比较好的解释。

可以先看这个:https://leetcode-cn.com/circle/article/GMopsy/

leetcode 双指针相关的题目:
https://leetcode-cn.com/tag/two-pointers/?utm_source=LCUS&utm_medium=banner_redirect&utm_campaign=transfer2china

参考资料:
https://hk029.gitbooks.io/leetbook/twopoint.html
https://www.geeksforgeeks.org/two-pointers-technique/
https://chocoluffy.com/2016/12/04/%E6%B5%85%E6%9E%90%E7%BB%8F%E5%85%B8%E9%9D%A2%E8%AF%95%E7%AE%97%E6%B3%95%E9%A2%98-two-pointer%E7%9A%84%E8%BF%90%E7%94%A8/

【0177_Week 00】关于数据结构和算法总览以及五毒神掌的理解

听了超哥的视频,算式重新回顾了,1.5倍速度看的,之前看的有点细致,再次看,将重点记录下来,主要还是以下两个框架。个人还是比较喜欢这样的分类方式的。
--数据结构分三个部分:1.一维数组;2.二维数组;3.特殊结构
--算法分为8个部分:3个基础部分。3.1.if-else,switch;3.2.while,for;3.3.递归。4.深度搜索;5.广度搜索。6.动态规划。7.二分查找。8.贪心算法。9.几何。后面的7、8、9接触还很少。
--关于五毒神掌的注意,第一次做题目的时候,不要用一两天时间去思考怎么做,给自己最多15分钟的思考时间,实在想不出来就看解题思路,然后,敲下来。第二遍的时候,就要完全自己写,就算是背也可以。第三遍就是一天时间过去以后,要重新再写一遍,这个时候,如果还是有些遗忘,可以回头去看看。最大的感觉就是,不要花太多时间去钻牛角尖,有不会的就去看答案,最开始之前的积极性要保持,这一点和做研究还是要区别。第四遍的时候,就是一周之后,根据记忆曲线来说,要给自己信心,不能AC,就要多多去背了。
最后,希望自己可以坚持下去,和大家一起多多交流。每天进步一点点。

【0305_week1】本周学习总结

本周学习总结

数据结构

  • 数组 Array

由于具有随机访问的特性,查询时间复杂度为O(1)。不过因为内存地址连贯的原因,增删时最坏的情况需要移动其他所有项目腾出位置才行,时间复杂度会比较高,为O(n)。

  • 链表 LinkedList

根据不同链表(单向链表,双向链表,循环列表)的特性,在数据中保存指向下一个、上一个节点地址的信息,因此内存地址可以分散开保存数据,增删时只需修改相应节点地址信息即可,时间复杂度十分稳定,为O(1)。不过正由于只有相邻节点才有可能知道下一个节点的存储地址,因此查询特定节点时需要从链表其中一头遍历到目标节点才可以。因此查询的时间复杂度为O(n)。

  • 跳表 SkipList

跳表属于基于空间换时间的概念来实现链表的数据结构,不只会保存相邻的节点地址信息,还会多保存特定间隔的节点地址信息,以此省略查询目标节点所需的步数。查询的时间复杂度为O(logn)。
但是正因为多保存了特定间隔的节点地址信息们,所以每次增删都需要更新相关节点的数据,因此增删的效率就没有传统链表来的高了。

  • 栈 Stack

具有后进先出的特性,增删操作时间复杂度为O(1)。但是查询的话需要遍历,因此时间复杂度为O(n)。

  • 队列 Queue

具有先进先出的特性,增删操作时间复杂度为O(1)。查询与栈一样需要遍历,因此时间复杂度为O(n)。

  • 双端队列 Deque

既可以在头部操作增删,也可以在尾部操作增删,类似于栈与队列的结合。时间复杂度与栈和队列相同,增删为O(1),查询为O(n)。

算法学习总结

除了要深入的了解数据结构之间的优劣,还需要利用五遍刷题法来对题目进行分解、学习和强化。先快速了解题干信息,然后在短时间内放开自己的思路收集灵感,之后对现有的优解进行学习解析,再多次巩固强化自己对题目解法的理解。在这个过程中,吸收优解的思路,将它化为自己的知识。

【0061_Week 01】 学习总结

1.刷题还是应该从最基础的题刷起,因为很多复杂的题的思路都是通过不同的简单题的思路组合才能解的
2.本周最主要的是链表,环状链表的题主要应用的是快慢指针法。正常的链表一般都可以通过翻转链表思路解题。一般数组相关的复杂题,可以首先考虑使用双指针法
3.本周刷题的时候速度还是比较慢的,因为在看题的解题思路的时候,很多不是太明白。自己总结还是感觉自己见的太少,继续努力。
4.在用5毒的方法刷题的时候,在第4毒的时候,有些时候发现某些题还是不能解出来,然后就强制自己吧这道题当作新题开始解。

【0139_Week1】关于算法学习方法的理解

    大学阶段算法也断断续续的学了不少年,一直不得法。每次都是从开始到放弃,循环往复,乃至于严重怀疑自己智商水平不适合学算法。看了覃老师的课程,并经过了一次课的学习之后,才恍然,原来死磕是错的!真的是错的!除了让自己觉得自己是弱智意外没有任何实际意义。远古时期的著名英雄Silencer经常教导我们:"Watch and Learn!",这才是学习的过程,对算法首先要保持谦卑和敬意,要先看懂、理解她,不要想着直接征服她。 
    在自认为看懂某道题或某类题之后就要像背单词一样去理解记忆,当天练、24小时后练、一周后练等等,训练对题目的肌肉(思维)反应,这会让自己在解更加复杂的题目时,不会将精力陷入对基础算法的实现障碍中。有了夯实的基础,在挑战更高难度的题目乃至实际工程问题时,才能游刃有余。

    以上是我第一周课程的理解。

【0019_week1】数组、链表的常用处理方式

对于数组的处理,常用到的是指针,并分为两种情况:

  1. 自定义指针:利用指针记录目标值将要存进数组中的位置,例如在 移动零删除排序数组中的重复项 这两个问题中,利用一个指针来记录下一个非零值、非重复值将要存进数组中的哪个位置。这类问题利用指针可减少空间复杂度,直接在数据内部进行操作,使其空间复杂度降为 O(1)

  2. 双指针夹逼:利用双指针左右夹逼的方法,遍历数组并求得目标值,这个方法在 盛最多水的容器 以及 三数之和 这两个问题中得到体现,当某目标值涉及到数组中多个值,利用双指针的方法可以避免嵌套遍历,从而降低时间复杂度,使其从 O(n^2) 降到 O(n)

对于链表的处理

  1. 快慢指针:涉及到环形链表,常会用到快慢指针。快指针一次走两步,满指针一次走一步,之后快慢指针是否重合、以及重合的位置都会对我们了解这个链表的具体情况有帮助,在 环形链表环形链表 II 两道题中都有对快慢指针的应用。

  2. 自定义指针:另一个关于链表的套路,就是在我们需要的地方(例如头指针之前)加自定义的指针,自定义指针随着方法的进行可以不断指向我们需要处理的链表数据,这样可以避免操作链表内部数据的指针指向时陷入混乱状态。这个套路也就类似于数组的自定义指针套路。

❤️算法训练营(第5期)预习周作业(选做)

作业:绘制自己的数据结构和算法脑图

要求:用脑图的方式把知识的脉络串联起来,不管对于学习新知识还是巩固已有知识,都是一种很好的学习方式。同学们可以将目前自己所掌握的数据结构和算法知识绘制成脑图,在绘制过程中可以查阅资料,补充目前掌握欠缺的部分,找到自己薄弱的地方。后面再通过课程的学习和刻意练习,动态地将自己绘制的脑图逐步补充、完善,从而达到真正的融会贯通。

脑图绘制工具不限。

0223_Week 01 一维数据结构的更多理解

学习心得:
一:掌握了查找Java数据结构及其API的方法;数组,链表,在源码中:栈是一个类,队列是一个接口,优先队列是一个类,双端队列是一个接口。
二:掌握这些一维数据结构的使用和适用的场景和环境,以及各自的特点。
三:优先队列的插入操作是O(1)的,取出操作是log(n)的,变慢了,因为是按照一定的优先级取出。
四:关于Queue和Deque源码的阅读分析:
Queue源码分析:
继承了集合的顶层Collection接口,提供的API接口有
Boolean add(E e):加入元素
Boolean offer(E e):在没有超出空间限制的情况下插入元素
E remove():移除队列的头元素
E poll():删除头节点
E element():
返回当前队列的头节点;
若队列为空将抛出:NoSuchElementException
E peek():查看当前队列的头元素。
PriorityQueue源码分析:
(1)继承了AbstractQueue的接口,以及实现了serializable接口,
(2)其中peek,clear,offer,poll等操作有利用到数组这样的数据结果为装载数据的原型载体
(3)实现了三种:无参,传递初始容量initialCapaicty,以比较器对象为参数的
等多种构造方法;
(4)通过调用比较器comparator规则,实现对PriorityDeque的初始化(元素vip定级)
(5)源码中实现了Iterator迭代器类的next()方法,以及hasNext()方法等
(6)其中实现提供了如下的API接口:
grow方法:扩充底层实现的数组的容量
add(E e);返回调用的是自身的offer(E e)方法;
offer():实现插入元素
poll():弹出一个元素
peek():查看队列头部的元素,返回的是 {return (E) queue[0];}
indexof():以数组为遍历对象,返回队列中的元素在数组中
remove():先是判断当前元素的下标是否越界,然后再移除指定位置的元素
。。。。。
五:关于利用新的API重写ppt上的代码:
import java.util.Deque;
import java.util.LinkedList;

public class Deque_realize{
public static void main(String[] args) {
Deque_realize de=new Deque_realize();
de.new_api_deque();

}
public void new_api_deque(){
    Deque<String> deque =new LinkedList<String>();
    deque.addLast("a");
    deque.addLast("b");
    deque.addLast("c");
    System.out.println(deque);

    String str=deque.peek();
    System.out.println(str);
    System.out.println(deque);
    
    while(deque.size()>0){
        System.out.println(deque.pop());
    }
    System.out.println(deque);
}

}

【0025_week1】学习总结

第一周学习总结
1.除了学习了一些数据结构的基本概念之外,还有一些根据特定数据结解决问题的小技巧如下
2.数组的 循环遍历 双指针的用法 、链表 要知道 next 节点 list->next 来分辨是什么类型的链表,
3.跳表其实就是升维的一种**。
4.更加让我意外和感觉到自己的不足的是自己其实连自己的语言的文档都没看过完整的一遍,像是 Stack 啊 LinkedList、Queue、PriorityQueue (可以定义谁先出) 其实语言文档中都有,且也有非常详细的用法。虽然开课才一周,越发感觉到的自己的基础的弱。
5.当然也有一些老师的经验,刷题方法的经验,写代码自顶向下的编程技巧,都有收获。
6.题要反复做,不理解的地方要反复听。一天进步一点点,一年就是一大点。加油。

[0221_Week 1]学习总结

学习笔记

Leetcode26题解题思路:
设置一个从1开始的指针进行遍历,当指针i 与i-1的值相等时,删除指针i的值,之后i要加1。
有个细节是,当i去重后,指针数少1,所以在删除了之后重复值之后,需要将i减一,在后面加一才会是在原点的位置。

Leetcode189题解题思路:
利用Python的切片特性,进行数组反转。
例[1,2,3,4,5,6] k=3

反转整体nums[::-1]----[7,6,5,4,3,2,1]
反转第一段nums[:k]----[5,6,7,4,3,2,1]
反转第二段nums[k:]----[5,6,7,1,2,3,4]

周总结:
一开始接触的,Python的切片很难理解,感觉挺抽象的,然后就自己在纸上涂涂画画,经过后面的几个特例,去记住他们的切片规则。
觉得:背会一个解法是最简单的,但是理解并学会运用,以及什么知识点该运用在什么地方才是最重要的。也许要达到这种水平还需要不断的练习,就想覃超老师说的,职业游戏选手都会单独练一个站位什么的,意思大概就是这个意思,所以这种训练方法以后可以加在自己的训练计划里面。就像之前学数学,数学一直以来都是强项,数学题练习多了就很容易能举一反三,还有就是覃超老师所说的找重复性。但是我认为,找重复性的基础是建立在练习多了,对题目的了解深了,才能进行聚义反三,当然,在做练习的时候,也要自己可以的去找之前做过的题目的重复性。
本来专业并非是编程的我,最后还是非常喜欢写代码,也许来算法课学习,并非完全是为了以后能升职加薪,因为刚毕业出来,目前的工作与算法无关,但是纯粹就是想学。每次跟同事说起学习技能的时候,说的最多的一句话就是,技多不压身。
除了为了以后,如果可以的话,重回编程路之外,还想提高自己思考问题的方式方法,想逼自己一把,不想太过颓废,得过且过的日子。

【0329_Week 01】 学习总结

学习笔记
// Deque
// addFirst
Class MyDeque implements Deque {
// 采用双向链表结构
private class Node {
int data;
Node next;
Node pre;
}

private Node head;
private Node end;
// 如果length=-1,则无容量限制;
private int length;
// 当前数据个数;
private int size;

public void addFirst(E object) {
    if (null == object) {
        throw new NullPointerException();
    }
    if (length != -1 && size == length) {
        throw new IllegalStateException();
    }
    // 采用双向链表添加
}

public void addLast(E object) {
    if (null == object) {
        throw new NullPointerException();
    }
    if (length != -1 && size == length) {
        throw new IllegalStateException();
    }
    // 采用双向链表添加
}

}

// PriorityQueue源码分析
1.采用对象数组Object[]来充当队列
2.使用比较器(或元素自带顺序)来排队
3.依次出队保证按照元素的顺序性(优先级)进行
4.插入一个元素时,必须按照优先级来,势必需要额外的时间和空间来维护该队列
5.删除一个元素,也需要O(n)的时间,在O(n)时间内找到该元素,删除时需要对对象数组做移动操作

学习总结:
1.非常重要的认知:以前学的算法知识真的只是基本概念,算法就是要尽可能使用高效的方法和数据结构来解决问题。
2.算法能力需要时间磨练,如同题海战术一般,需要不断去打磨,去积累经验。
3.算法犹如数据,有很多的**在里面,并且需要知晓用计算机来完成,那么可用的方式和策略无非就是分支/循环/递归这些基本结构,找最近重复子问题,找相关性等等。
4.非常认同老师的【五毒神掌】方法,后续会坚持使用该方法,多思考,多回顾,多总结。
5.特别的,这周了解到的比较重要的**有两个,分别是:夹逼法则和以空间换时间。

【0149-week1】学习总结

数据结构

本周的学习了数组、链表、跳表、栈、队列、优先队列、双端队列。在学习到的这些数据结构本质还是数组和链表两种基础结构。其它数据结构都可以基于数组和链表两种基本数据结构来实现。关于数组和链表的区别如下:

结构 查找 更新 插入 删除
数组 O(1) O(1) O(n) O(n)
链表 O(n) O(1) O(1) O(1)

我大部分时间都是在写javascript(js),js官方只提供了数组而没有链表,我觉得这挺可惜的。虽然js提供的数组功能功能很丰富,但是链表的实用价值还是很高的。我在实践中也是大量使用数组而没有意识到链表的价值。在课后练习题leetcode的【239.滑动窗口最大值】中我使用【641.设计循环双端队列】实现的链表来替代数组后,运行效率从击败69%的提交记录提升到了100%。根据不同的应用场景使用正确的数据结构是一个提升运行效率的最省力的办法。

解题方法

  • 嵌套循环
  • 双指针(双向夹逼)
  • 复杂问题拆分为重复的子问题
  • 临近相关问题采用栈的**
    在以前解题时我偏向于自己想出解题思路并实现,这个过程中往往会碰到很多的问题,然后不断解决,最终写出的方法跟别人题解相比往往很“笨”。最坏的是尽管之后学习了别人的方法,但是在之后遇到同样问题是往往第一个冒出的想法还是第一次费劲写出的“笨”代码。所以正确的刷题方法很是重要,五遍刷题法的精髓就是在于不断强化记忆和学习别人的优秀解题思路。然后在熟悉了更多的相似类型的题目的情况下才能提炼出属于自己解题方法,正所谓书读百遍其义自见。

[0111-week1] 学习总结

本周分析PriorityQueue 源码,深刻理解了完全二叉树,例如:它的原理、运用场景,如何通过代码删除、插入元素。
保持着对源码的信任,它构建数据结构的方式值得我去掌握。
在分析过程中时不时有这样的疑问?是否有必要花这么多时间去理解底是如何实现的,理解后是否有必要再花时间去掌握它。
当前觉得还是有必要,现在可能找不到很好的理由。
通过对源码的分析,感觉有三个好处,一是理解这类数据结构,二是了解它的实现,三是对编写代码带来的帮助。

【0115第一周学习总结】

第一部分课后作业,第二部分学习总结’

第 4 课的课后作业:

1.用add first或add last这套新的API改写Deque的代码:

import java.util.Deque;
import java.util.LinkedList;
public class ModifyDeque {
    public static void main(String[] args) {
        Deque<String> deque = new LinkedList<String>();
        deque.addFirst("a");
        deque.addFirst("b");
        deque.addFirst("c");
        System.out.println(deque);
        String str = deque.peekFirst();
        System.out.println(str);
        System.out.println(deque);
        while (deque.size() > 0){
            System.out.println(deque.removeFirst());
        }
        System.out.println(deque);
    }
}

2.分析Queue和Priority Queue的源码:
Queue的源码示例地址:http://fuseyism.com/classpath/doc/java/util/Queue-source.html
首先Queue是一个Interface,Queue队列继承了Collection接口,并扩展了队列相关方法。是一种为了可以优先处理先进入的数据而设计的集合。

boolean add(E e);
在不超出规定容量的情况下可以将指定的元素立刻加入到队列,成功的时候返回success,超出容量限制时返回异常。

boolean offer(E e);
前面跟add()一样,就是与add相比,在容量受限时应该使用这个。

E remove();
检索并删除此队列的首元素,队列为空则抛出异常。

E poll();
检索并删除此队列的首元素,队列为空则抛出null。

E element();
检索但并不删除此队列的首元素,队列为空则抛出异常。

E peek();
检索但并不删除此队列的首元素,队列为空则抛出null。

我的理解就是:数据只能从队尾进来,从队首离开,跟人在实际生活中的排队很像,先进来的人先离开。Queue严格遵循了这个原则,使插队和提早离开变得不可能。

Priority Queue的源码示例地址:http://fuseyism.com/classpath/doc/java/util/PriorityQueue-source.html
PriorityQueue继承于Queue,相比于一般的队列,它的出队的时候可以按照优先级进行出队,PriorityQueue可以根据给定的优先级顺序进行出队。

主要属性:
private static final int DEFAULT_CAPACITY = 11;
默认的容量。

E[] storage;
元素存储的地方。

int used;
数组中的实际元素数量。

Comparator comparator;
比较器。

主要方法:
6种构造函数:

public PriorityQueue()
  71:   {
  72:     this(DEFAULT_CAPACITY, null);
  73:   }
  74: 
  75:   public PriorityQueue(Collection<? extends E> c)
  76:   {
  77:     this(Math.max(1, (int) (1.1 * c.size())), null);
  78: 
  79:     // Special case where we can find the comparator to use.
  80:     if (c instanceof SortedSet)
  81:       {
  82:         SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
  83:         this.comparator = (Comparator<? super E>) ss.comparator();
  84:         // We can insert the elements directly, since they are sorted.
  85:         int i = 0;
  86:         for (E val : ss)
  87:           {
  88:             if (val == null)
  89:               throw new NullPointerException();
  90:             storage[i++] = val;
  91:           }
  92:       }
  93:     else if (c instanceof PriorityQueue)
  94:       {
  95:         PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
  96:         this.comparator = (Comparator<? super E>)pq.comparator();
  97:         // We can just copy the contents.
  98:         System.arraycopy(pq.storage, 0, storage, 0, pq.storage.length);
  99:       }
 100: 
 101:     addAll(c);
 102:   }
 103: 
 104:   public PriorityQueue(int cap)
 105:   {
 106:     this(cap, null);
 107:   }
 108: 
 109:   public PriorityQueue(int cap, Comparator<? super E> comp)
 110:   {
 111:     if (cap < 1)
 112:       throw new IllegalArgumentException();      
 113:     this.used = 0;
 114:     this.storage = (E[]) new Object[cap];
 115:     this.comparator = comp;
 116:   }
 117: 
 118:   public PriorityQueue(PriorityQueue<? extends E> c)
 119:   {
 120:     this(Math.max(1, (int) (1.1 * c.size())),
 121:          (Comparator<? super E>)c.comparator());
 122:     // We can just copy the contents.
 123:     System.arraycopy(c.storage, 0, storage, 0, c.storage.length);
 124:   }
 125: 
 126:   public PriorityQueue(SortedSet<? extends E> c)
 127:   {
 128:     this(Math.max(1, (int) (1.1 * c.size())),
 129:          (Comparator<? super E>)c.comparator());
 130:     // We can insert the elements directly, since they are sorted.
 131:     int i = 0;
 132:     for (E val : c)
 133:       {
 134:         if (val == null)
 135:           throw new NullPointerException();
 136:         storage[i++] = val;
 137:       }
 138:   }

清空队列:

 140:   public void clear()
 141:   {
 142:     Arrays.fill(storage, null);
 143:     used = 0;
 144:   }

比较器:

 146:   public Comparator<? super E> comparator()
 147:   {
 148:     return comparator;
 149:   }

迭代器:

 151:   public Iterator<E> iterator()
 152:   {
 153:     return new Iterator<E>()
 154:     {
 155:       int index = -1;
 156:       int count = 0;
 157: 
 158:       public boolean hasNext()
 159:       {
 160:         return count < used;
 161:       }
 162: 
 163:       public E next()
 164:       {
 165:         while (storage[++index] == null)
 166:           ;
 167: 
 168:         ++count;
 169:         return storage[index];
 170:       }
 171: 
 172:       public void remove()
 173:       {
 174:         PriorityQueue.this.remove(index);
 175:     index--;
 176:       }
 177:     };
 178:   }

添加节点:

 180:   public boolean offer(E o)
 181:   {
 182:     if (o == null)
 183:       throw new NullPointerException();
 184: 
 185:     int slot = findSlot(-1);
 186: 
 187:     storage[slot] = o;
 188:     ++used;
 189:     bubbleUp(slot);
 190: 
 191:     return true;
 192:   }

获取优先级队列头结点:

 194:   public E peek()
 195:   {
 196:     return used == 0 ? null : storage[0];
 197:   }

移除并获取优先级队列头节点:

 199:   public E poll()
 200:   {
 201:     if (used == 0)
 202:       return null;
 203:     E result = storage[0];
 204:     remove(0);
 205:     return result;
 206:   }

移除指定元素:

 208:   public boolean remove(Object o)
 209:   {
 210:     if (o != null)
 211:       {
 212:         for (int i = 0; i < storage.length; ++i)
 213:           {
 214:             if (o.equals(storage[i]))
 215:               {
 216:                 remove(i);
 217:                 return true;
 218:               }
 219:           }
 220:       }
 221:     return false;
 222:   }

队列中元素个数:

 224:   public int size()
 225:   {
 226:     return used;
 227:   }

添加所有元素:

 231:   public boolean addAll(Collection<? extends E> c)
 232:   {
 233:     if (c == this)
 234:       throw new IllegalArgumentException();
 235: 
 236:     int newSlot = -1;
 237:     int save = used;
 238:     for (E val : c)
 239:       {
 240:         if (val == null)
 241:           throw new NullPointerException();
 242:         newSlot = findSlot(newSlot);
 243:         storage[newSlot] = val;
 244:         ++used;
 245:         bubbleUp(newSlot);
 246:       }
 247: 
 248:     return save != used;
 249:   }

寻找插槽(没完全理解):

 251:   int findSlot(int start)
 252:   {
 253:     int slot;
 254:     if (used == storage.length)
 255:       {
 256:         resize();
 257:         slot = used;
 258:       }
 259:     else
 260:       {
 261:         for (slot = start + 1; slot < storage.length; ++slot)
 262:           {
 263:             if (storage[slot] == null)
 264:               break;
 265:           }
 266:         // We'll always find a slot.
 267:       }
 268:     return slot;
 269:   }

移除指定序号的元素:

 271:   void remove(int index)
 272:   {
 273:     // Remove the element at INDEX.  We do this by finding the least
 274:     // child and moving it into place, then iterating until we reach
 275:     // the bottom of the tree.
 276:     while (storage[index] != null)
 277:       {
 278:         int child = 2 * index + 1;
 279: 
 280:         // See if we went off the end.
 281:         if (child >= storage.length)
 282:           {
 283:             storage[index] = null;
 284:             break;
 285:           }
 286: 
 287:         // Find which child we want to promote.  If one is not null,
 288:         // we pick it.  If both are null, it doesn't matter, we're
 289:         // about to leave.  If neither is null, pick the lesser.
 290:         if (child + 1 >= storage.length || storage[child + 1] == null)
 291:           {
 292:             // Nothing.
 293:           }
 294:         else if (storage[child] == null
 295:                  || (Collections.compare(storage[child], storage[child + 1],
 296:                                          comparator) > 0))
 297:           ++child;
 298:         storage[index] = storage[child];
 299:         index = child;
 300:       }
 301:     --used;
 302:   }

冒泡排序的函数:

 304:   void bubbleUp(int index)
 305:   {
 306:     // The element at INDEX was inserted into a blank spot.  Now move
 307:     // it up the tree to its natural resting place.
 308:     while (index > 0)
 309:       {
 310:         // This works regardless of whether we're at 2N+1 or 2N+2.
 311:         int parent = (index - 1) / 2;
 312:         if (Collections.compare(storage[parent], storage[index], comparator)
 313:             <= 0)
 314:           {
 315:             // Parent is the same or smaller than this element, so the
 316:             // invariant is preserved.  Note that if the new element
 317:             // is smaller than the parent, then it is necessarily
 318:             // smaller than the parent's other child.
 319:             break;
 320:           }
 321: 
 322:         E temp = storage[index];
 323:         storage[index] = storage[parent];
 324:         storage[parent] = temp;
 325: 
 326:         index = parent;
 327:       }
 328:   }

扩容:

 330:   void resize()
 331:   {
 332:     E[] new_data = (E[]) new Object[2 * storage.length];
 333:     System.arraycopy(storage, 0, new_data, 0, storage.length);
 334:     storage = new_data;
 335:   }
 336: }

学习总结

1.说实话,题目虽然以前做过,但都是周日一天赶出来的,有几道也是复制粗来的。
2.印象最深刻的就是快慢指针。
“快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2,慢指针每次向前移动1次。”
感觉真的就是非常巧妙的设计,解决一些链表问题,非常有用。
3.另外学习了一下排序的一些算法。
4.努力跟上课程的进度,跟上大家的额步伐。

【0347_week1】学习总结

第一周的算法学习总体上还算比较轻松愉快,感触最深的大致为一下几点:

  1. 五毒神掌,有意识地重复刷题。以前学习算法的过程中,习惯每道题只做一遍,虽然可以从题解中了解到非常精妙的解答,但是不重复练习,很快就会忘记。
  2. 使用多种思路解题,平时自己在做算法题目的时候,更倾向于找最优解,对于暴力法这种解题思路视而不见,跟着老师的学习,学会了一步一步学习不同的解题思路。
    3.认识到了一个非常重要的理论:所有复杂的算法最终都会归结于最近重复性(if else for loop recursion)
    4.在学习这些基本数据结构中,学习到通过看别人实现的相关数据结构的源码来增进对数据结构的理解和认识

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.