https://leetcode.com/problems/symmetric-tree/
Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).
1 | # Definition for a binary tree node. |
https://leetcode.com/problems/largest-rectangle-in-histogram/
Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.
在height后面加上一个0作为右边界,记录高度堆栈stack加上一个-1作为左边界。
遍历heights,若当前height不低于stack最后一个元素的高度时,添加进stack中。否则:
从堆栈中弹出最后一个元素,即当前最高处位置信息,获取该位置的高度。
以当前位置i作为右边界,当前堆栈的最后一个元素stack[-1],即当前第二高的位置作为左边界,宽度则为右边界-左边界-1(边界均不包含在内),即w=i-stack[-1]-1。
此时矩形面积即为h*w。
1 | class Solution: |
https://leetcode.com/problems/maximal-rectangle/
Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area.
大致思路和 84. Largest Rectangle in Histogram 相同,对于每个row判断如果为1则高度叠加,否则高度清零。
1 | class Solution: |
https://leetcode.com/problems/unique-binary-search-trees/
Given n, how many structurally unique BST’s (binary search trees) that store values 1 … n?
对于n个值,以i为根,则可以将[1,i-1]放入左边,[i+1,n]放入右边,因此有[i-1]*[i-j]种方式。
1 | class Solution: |
https://leetcode.com/problems/validate-binary-search-tree/
Given a binary tree, determine if it is a valid binary search tree (BST).
当节点为None时,即到达分支底端,返回True即该条线正确。
当min存在且当前值小于min时,返回False。
当max存在且当前值大于max时,返回False。
当不存在min和max时,开始往两头搜索。左边:最小值维持原样,最大值为当前节点的值。右边:最大值维持原样,最小值为当前节点的值。
1 | # Definition for a binary tree node. |
https://leetcode.com/problems/word-search/
Given a 2D board and a word, find if the word exists in the grid.
The word can be constructed from letters of sequentially adjacent cell, where “adjacent” cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.
1 | class Solution: |
https://leetcode.com/problems/merge-intervals/
Given a collection of intervals, merge all overlapping intervals.
先对数组排序,然后遍历数组。设定两个变量记录左边界和右边界。
对于每个范围,当其左值小于记录的右边界时,即这个范围可以被merge。此时右边界为右值与右边界的最大值,在结果数组更新最后一项的右值。
否则重新设定左边界与右边界,并将新的范围记录至结果中。
1 | class Solution: |
https://leetcode.com/problems/rotate-image/
You are given an n x n 2D matrix representing an image.
Rotate the image by 90 degrees (clockwise).
You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.
1 | class Solution: |
https://leetcode.com/problems/jump-game/
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Determine if you are able to reach the last index.
设定一个变量reachable记录当前能够达到的最远值。然后遍历nums:
当reachable<i时,即已经不能到达该处。返回False。
否则更新reachable,大小为当前能走的最远值以及此处可以走的最远值之间的最大值。
接着判断reachable是否大于nums,若是即可提前返回True跳出。
1 | class Solution: |
https://leetcode.com/problems/permutations/
Given a collection of distinct integers, return all possible permutations.
Backtrack。当temp长度等于所有数字时,添加进结果。否则遍历数字,每个数字添加进temp中,进行下一次迭代。当temp中存在相同数字时跳过。
1 | class Solution: |
https://leetcode.com/problems/next-permutation/
Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.
If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).
The replacement must be in-place and use only constant extra memory.
Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
指针i从后往前找到第一个前面比后面小的数nums[i-1],此时i:end均为从大到小排序。
指针j从后往前找到第一个比nums[i-1]大的数nums[j]。此时nums[j]为i:end中的最小数。
交换nums[i-1]与nums[j],此时i:end依然为从大到小排序。
将i:end倒序,此时变成从小到大排序。完成。
1 | class Solution: |
https://leetcode.com/problems/trapping-rain-water/
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
使用双指针,分别从两头往最高处逼近。每次经过刷新左边和右边的最高值,该处结果等于最高值与当前值的差。
1 | class Solution: |
https://leetcode.com/problems/remove-nth-node-from-end-of-list/
Given a linked list, remove the n-th node from the end of list and return its head.
Follow up:
Could you do this in one pass?
使用两个指针fast以及slow,fast先出发,slow相隔n位再出发。当fast走到尽头时slow跳过下一指针即可。
1 | # Definition for singly-linked list. |
https://leetcode.com/problems/generate-parentheses/
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
使用动态规划,分析子问题:每次加括号要么加在外面,要么加在旁边。
1 | class Solution: |
https://leetcode.com/problems/two-sum/
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
使用hashmap查表,拿到数字首先查找余值是否在表内,若在的话直接返回表内的对应值。接着将数字以及index存入表内。
1 | import java.util.HashMap; |
https://leetcode.com/problems/container-with-most-water/
Given n non-negative integers a1, a2, …, an , where each represents a point at coordinate (i, ai). nvertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
Note: You may not slant the container and n is at least 2.
贪心算法,从左右两边开始,从短的一边往中间缩进并记录最大值。由于短板效应从短的一边往中间缩进是最有利的。
1 | class Solution: |
https://leetcode.com/problems/longest-palindromic-substring/
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
马拉车方法 (Manacher’s Algorithm) 利用回文的性质减少运算。
考虑到回文中心可能位于两字符之间,将字符串之间的间隔使用符号(#)代替。
定义radius数组,记录每个位置的最大回文半径。
对于一个回文串的中心c,令R为这个回文串的右半径(R=c+radius[c]),即回文串范围为[c-R, c+R]。根据回文的性质,右半径内的字符 i(i->(c, c+R))其回文半径与回文串左半径内的镜像字符 j(j=(2*c-i))有关。当:
j的回文范围在回文串范围以内时,i的回文与j的回文完全一致。 (radius[i]=radius[j])
j的回文范围在回文串范围以外时,只能确保i的回文范围在右半径R之内。 (radius[i]>=R-i+1)
尝试扩展i的回文范围,考察T[i+radius[i]]==T[i-radius[i]]。若属于3.1情况将直接跳出。若属于3.2情况将扩大i的回文范围。
当i的回文范围大于右半径R时,将该位置作为中心(c=i),定义右半径R(R=c+radius[c]),重复步骤345。
记录radius以及回文串的最大值。所有位置遍历完后返回最大回文串。
1 | class Solution: |
GTD®(又称“尽管去做”,Getting Things Done®) 源自David Allen的同名书籍,是一种行为管理的方法。这种方法要求个人将目前正在进行的所有工作以及任务使用 记录 的方式(通过纸笔、电子设备等)从头脑中移出来,使得这些外部设备充当 “第二大脑”,让自己 不再需要在进行一项工作的同时惦记着其他未完成的工作,从而能够专心致志地完成当前的工作,在同时面对一大堆工作的时候也能够有条不紊地处理。
不过,GTD®所承担的“第二大脑”,又和印象笔记所推崇的“第二大脑”有一定区别。在印象笔记中,“第二大脑”所承担的任务是让外部设备辅助记下各种生活中的资料以及想法,而GTD®的作用则更像是RPG游戏中的“任务列表”:让外部设备来辅助记下目前以及以后需要做的工作。也许有人会说,这不就是Todo嘛,是的,但又不是。其实GTD®不过是经过改造的一种Todo的使用方式。但这种新的使用方式却让Todo的效率有了极大提高。也因为如此,只要有一款上手的Todo应用,经过些许调整就能够无缝转换到GTD®的方式来,转换成本非常低。
对于第一次接触GTD®的人来说,最全面的入门方式当然就是去看原著。网上也有中文的翻译版本可以参考。不过原书由于出版的年代比较久远,使用的是比较原始的纸张记录的方式,对于互联网以及无纸化办公普及的今天相比有些落后。对于没有碰过纸笔快一年的我来说,再用回硬核的笔记本的方法实在太麻烦了。万幸的是手机里还藏着一款Todo应用—— 2Do。因此在读完全书以后,我尝试将书中的概念逐一映射到2Do里面,总结了一套使用2Do作为GTD®工具的方案。经过两三个月的修改以及使用,自以为这套方法已经进化得比较成熟了,所以在这里放出来给大家参考一下。
2Do是一款比较全面的Todo类应用。它支持iOS/Mac/Android三种平台,可以添加任务(Task)
、项目(Project)
、以及清单(Checklist)
三种任务类别,并且能够为每项任务标记标签(Tags)
;在分类功能中,2Do支持进行分类的一般列表(List)
,以及最重要的 智能列表(Smart List) 。智能列表实际上是一个可保存筛选参数的筛选器,他可以从所有任务中按照条件筛选出符合要求的任务并且展示出来。通过创建一系列智能列表我们就可以得到一系列内容随任务状态而改变的动态列表,这也是在这套方法中GTD®能够实现的基础。具体的细节会在实际操作中进行演示。
从上图可以看出,在GTD®的概念中,所有的想法(以及工作,以及任务)首先应该去到的地方应该是”In”,也就是 Inbox 。意思是说,当脑海中突然闪现某种想法的时候,或者有一大堆任务等待着倾诉的时候,应当第一时间把它们扔进 Inbox 区域中。不过在这里,与一般Todo记录事件的方式不同在于,GTD®不要求第一时间将添加进来的记录进行分类。Inbox作为事件的缓冲区域,所有进来的内容无需做任何处理,直接扔进来就好了。因此可以看到我的 Inbox 区域基本上是杂乱无章的。
因此,制定一个专门的时间用于整理 Inbox 中的内容显得尤其重要。在整理的时候,首先需要制定一个规则。必须从上往下,或从下往上处理所有当前的内容,而不允许有跳过的情况。这一点Allen在书中曾明确要求过,就是为了杜绝积压的现象出现。因为假如有一条记录一直压在Inbox中从来不去处理,那么久而久之就会习惯积压事件,那么 Inbox 就失去了作为 事件缓冲区 的意义。
对于事件的整理部分,GTD®制定了一系列要求。对于每一条记录,首先应当考虑的是“下一步是什么?”。比如上面截图中的某一条记录Blog for 2Do:
因为Blog for 2Do是一个工作,因此GTD®要求列出这个工作的接下来需要进行的具体操作:
为2Do写博客:
- 新建一份Markdown文档
- 查找GTD资料
- 在2Do中新建一份适当的演示用事件
- 提前操作一遍并记录实际操作
- 整理最终文档
- 发布博客
因此,我将这条记录的类别由默认的任务(Task)
变成项目(Project)
。在2Do中,一个项目
中可以包含多个任务
,因此很适合用来记录 下一步动作。
值得一提的是,原始GTD®只需要考虑这份工作的 下一步动作(Next Action) 是什么,注意是Action而不是Actions。因此理论上一个工作的列表中最后一项就是当前需要完成的下一步动作,完成一项动作之后再继续记录下一项。但是我习惯于一次性把所有步骤都考虑好,只记录一项只会让我在脑海里不断地演绎下一步下一步。。。反而更加让我寝食不安了。因此我把所有步骤都记录下来了。如果习惯一步一步来的同学也可以使用GTD®的正统做法,只记录一步。 当然2Do的标签功能告诉我,这不要紧。好奇的同学可以继续看下去:)
接下来,GTD®要求将这份工作按照使用场景作分类。在2Do中,可以简单地使用一般列表(List)
对各个场景进行分类。对于 能够进行操作的任务,我目前分有个人(Personal)
以及工作(Work)
两个类别。对于 不能立即进行 的任务,我设置了稍后阅读(Read later)
以及点子/以后再做(Ideas/someday)
两个类别。因此写博客的任务便分配到了个人
列表中。
有的人产生想法的频率很快,Inbox很快就塞满了,因此这些人可能需要早中晚分别整理一次。对于我来说,每天中午整理一次也许就够了。在空闲的时间没事就拿出手机整理当前的想法也是一种很好的方式。
制定了事件的 下一步动作 以后,我们需要进一步考虑这个动作是属于以下哪个方面的:
- Do it! - 如果这个动作小于两分钟而且现在能够完成的话,不需任何标记,立刻就去做!
- Delayed - 如果这个动作要到特定某个日期才能完成,这就是 滞后(Delayed) 状态。
- Await - 如果这个动作需要委托他人完成,那么就是 等待(Await/Waiting For) 状态。
- Next - 如果这个动作不属于上面的每一项,或者当前条件不允许立刻完成的,那么这个动作就要放入你的 下一步动作(Next Actions) 列表了。记录下来,在合适的时候完成它!
在2Do中,有一项非常实用的功能:标签(Tags)
。通过标签我们就可以将每一个动作使用next
,await
以及delayed
三个标签作为标记。如果你使用的是原始GTD®的做法,一次只记录一项内容,那么你可以很轻松地、直接对这项动作进行标记。如果你和我一样,是习惯于提前规划一切的选手,那么请记住,只对第一项动作进行标记。(已完成的任务请直接通过app标记已完成
,这样的话就能够保证最上面的动作就是当前需要进行的下一步动作。)
经过几个月的使用,我给自己制定了一套比较合理的标记规则,目前使用上是比较舒服的:
next
- 意味着直接的下一步动作。永远记得需要对工作列表最上面的一项没有任何标记的内容标记上next
。await
- 当一项动作因为外部因素目前无法继续进展的,且不知道明确完成时间的(比如正在等待别人答复的事情),标记上await
,并且在note的地方记录下不能完成的原因,使得在检查列表的时候能够快速记起这件事情为什么正在处于等待状态。delayed
- 当一项动作因为自身的理由暂时搁置的(比如“需要等到出国前一天再开通国际漫游服务”等等),则需要标记上delayed
,并且需要标记上截止时间(使得app能够在当天及时提醒你继续进行操作)。
有同学会说,标记了以后如何查看呢?在这里隆重介绍2Do最引以为傲的功能——智能列表(Smart List)。正如之前所提到的,智能列表实际上就是保存了筛选参数的筛选器。因此我们只需要选出包含动作标记的任务,便能立刻创造出各个标记专属的列表了。对于需要关注的 下一步动作 列表,我们可以建立一个筛选器,提取出所有标签为next
的就好了:
1
tags: next
可以看到,我们刚刚标记的Finish the doc动作也包含在其中了。同样地,await
,delayed
标签同样也可以使用这种方式创立属于不同状态的专属列表:
1
2tags: await
tags: delayed
添加内容固然很重要,但即时检查并且回顾工作进展也是非常重要的一项工作。需要约定一个固定时间定期对所有列表中的工作进行检查。检查有没有新的下一步动作没有进行标记的,或者哪一些滞后或者等待的内容现在可以继续进行下去的。对于我来说,每天熄灯睡觉在床上玩手机的时候就是一个最佳的回顾时间。
至此,GTD®所要求的所有标准都成功移植进2Do应用里了!简单吧,GTD®本身一点都不复杂。但使用上GTD®以后,给生活带来的便利确是巨大的。至少在使用了两三个月以后,尽管因为拖延症,当前需要同时进行的工作再多,脑子里也不会一直环绕着各种未完成事项的提醒了,处理事情的时候也能够心无旁骛了。至少,因为不会忘记处理任何滞后的事情,拖延症也不再有副作用了!!
]]>无论是对服务器进行身份校验,还是端对端进行聊天,所有内容都将使用非对称加密方式进行加密后传输。就算被中间人攻击,获取到的不过是一串184个字符的BASE64编码,在没有获取到接收端私钥的情况下根本无从破译。
对于发送聊天信息的客户端,每次发送消息前将向对方发送一个握手包请求对方发送公钥。将聊天内容使用公钥加密后直接传送至对方客户端。而接收端在每次收到握手请求后将生成一组新的密钥对。密钥对使用一次后将被销毁,即使有一条截获的消息被破解,那么对于其他消息还能保持加密。
客户端对于服务器的通信也是使用RSA加密的方式进行。服务器使用的是固定密钥对,所以整套系统安全性的突破口在于服务器密钥的保管。由于服务器端是整套系统安全性最低的部分,所以服务器承担的工作是最不敏感的,仅为验证身份信息以及承担寻呼的任务,除此以外完全不干涉客户端之间的通信。
客户端与服务器端在发送数据时都需要获取对方的公钥,然后通过加密类CyptorUtil
进行加密操作再进行发送。
发送方处理流程
原始数据->RSA加密->BASE64编码->发送
接收端处理流程
接收->BASE64解码->RSA 解密->原始数据
1 | public class CryptorUtil { |
客户端开启一个socket
连接至ServerSocket
,通过自定义通信协议发送加密信息。服务器接收到信息后解密并解析指令,进行相应的操作并发送返回的加密信息给客户端。
1 | public class Xcr3TServer{ |
1 | public class SocketHandler implements Runnable { |
客户端在登录后将开启一个SocketServer
,并提交该SocketServer
的IP和端口号给服务器记录。当有其他客户端向服务器请求与该客户端进行通信时,服务器将返回该客户端的IP和端口号。其他客户端将直接对该地址创建一个socket
进行连接。
1 | public class Xcr3TClient { |
1 | public class ChatHandler { |
为了方便后续进行跨平台通信,制定了一套 RESTful API 作为通信规范。发送方使用Request.Builder()
、Response.Builder()
生成符合规范的请求头,使用RequestParser
、ResponseParser
解析符合规范的请求。
用户操作 | 发送至 | 协议头 | 附加数据 | 对方返回数据 |
---|---|---|---|---|
注册用户 | 服务器 | POST /register HTTP/1.1 | {uid,identity,publicKey} | {status,uid,id} |
登录 | 服务器 | POST /handshake HTTP/1.1 | {uid,identity,port,publicKey} | {status,token} |
寻找用户 | 服务器 | POST /find HTTP/1.1 | {destUID,token} | {status,valid,ready,ip,port} |
[聊天]握手 | 对方客户端 | POST /handshake HTTP/1.1 | {uid,encryptID,publicKey} | - |
[聊天]获取公钥 | 对方客户端 | GET /key HTTP/1.1 | - | {encryptID,publicKey} |
[聊天]发送信息 | 对方客户端 | POST /chat HTTP/1.1 | {chat,decryptID} | - |
[聊天]关闭会话 | 对方客户端 | POST /goodbye HTTP/1.1 | - | - |
登出 | 服务器 | POST /goodbye HTTP/1.1 | {uid,token,publicKey} | {status,goodbye} |
1 | public class Xcr3TProtocol { |
1 | public class Request { |
1 | public class Response { |
1 | public class RequestParser { |
1 | public class ResponseParser { |
为了能够进行客户端的快速开发,创建了一套Java API,通过调用相关方法即可快速开发出一个符合规范的Java客户端。
API分为三部分,分别是:
Chattable
接口:提供信息的回调方法。Xcr3TClient
类:客户端主类,可调用其公有方法完成上述所有用户操作。Xcr3TAdapter
类:客户端适配器类,支持多客户端、命令行操作,传入Chattable
类后能够支持信息回显。
1 | public interface Chattable { |
1 | public class Xcr3TAdapter { |
ChatterUI 是一个使用 Java API 进行快速开发的示例。它通过创建Xcr3TAdapter
对象对多个客户端进行管理,实现Chattable
接口以实现信息回显的功能。在文本框内键入命令行进行操作。也可以鼠标点选右方的列表栏进行客户端与会话的管理操作。
1 | public class ChatterUI implements Chattable { |
本次实验工程量巨大,从一开始的功能制定就画了张大饼,接下来的工作就是竭尽全力去填满这张大饼。对于每个功能的流程构思,我使用了软件工程里面的开发方式,先画出各项功能的流程图,不得不说这种方式在开发一套复杂系统时能够保持思路的清晰,不至于一不小心迷失在细节的深渊当中。在构思和实现这套加密算法时,我对RSA、MD5操作、字符串转BASE64操作有了更深刻的认识。而写的这个加密类是我非常满意的一个地方。这个加密类使得复杂的加密解密过程被包装在了简单的两个方法pack()和unpack()中,使得编程效率大大的提高,这也是全局RSA加密得以实现的基础。对于跨平台开发,虽然这次项目提交的时候并没有实现一个跨平台的客户端,但是为此制定的一套RESTful API也是本次项目的一个亮点。因为通过这套API确实看到了跨平台开发的曙光。虽然这个作业提交了,但是这个项目还远远没有结束。我从中看到了一个非常大的可能性,所以并不会放弃该项目,会将持续对其进行开发。
]]>深大统一身份认证平台使用了Jasig Central Authentication Service 3.5.2.1
这个 CAS 平台。CAS,全称 Central Authentication Service,是一种比较不错的的单点登录服务框架。使用像 CAS 这样单点登录(Single Sign On, 简称 SSO)方案时,用户只需要登录一次即可访问所有相互信任的系统(比如深大内的各种站点)。
CAS的基本验证原理如图所示:
通过原理图可知,浏览器访问一个使用了 CAS 服务的页面(CAS Client,如BlackBoard)后,将会被重定向至 CAS 验证页面(CAS Server,如深大的统一身份认证平台)进行用户认证。当用户验证成功后,浏览器将获得一条唯一且不可伪造的 Ticket
。然后 CAS Server 页面将浏览器重定向回 CAS Client 页面,浏览器紧接提交获得的Ticket
给 Client页面,Client 页面接收到Ticket
后,从后台验证该Ticket
的合法性。验证通过后将自动跳转到登录后的页面,并返回相关的身份信息。
请求 BlackBoard 校园卡用户页面
http://elearning.szu.edu.cn/webapps/cbb-sdgxtyM-BBLEARN/checksession.jsp,成功打印登录后的 BlackBoard 页面内容:
实现有两部分代码组成。第一个是主逻辑代码HTTPSPoster.java
:
1 | package cn.kavel.httpsposter; |
第二个是用于绕过证书验证的自定义TrustMamagerMyX509TrustManager.java
:
1 | package cn.kavel.httpsposter; |