数组与字符串
二叉堆可以用数组来表示
如大顶堆,构建的过程为从下到上。每次出现子节点大于父节点,旋转,然后对当前节点的子树进行递归处理,打印出排序的堆的时间复杂度为,nlog(n).
1.1确定一个字符串的所有字符是否全都不同。
思路:采用boolean数组进行缓存,ASCII与Unicode,考虑进去长度的因素256个,大于肯定会出现重复,时间复杂度O(n)。
1.2用c或者c++实现void reverse(char* str),反转一个null结尾的字符串。
思路:不使用额外的空间就可以实现,这个是双指针。
这里给出程序
void reverse(char *str){
char *end = str;
char temp;
if (str){
while(*end){
++end;
}
--end;
while (str < end){
temp = *str;
*str++ = *end;
*end-- = temp;
}
}
}
1.3给定两个字符串,请编写程序,确定其中一个字符串重新排列后能否变成另外的一个字符串。
思路:先去定细节,变位词Dog与God疏忽是同一个字符串,空格出现算不算,String.trim()去除首尾的空格。
解法1:可以先对字符串进行排序处理,String.toCharArray(),java.util.Arrays.sort()方法
解法2:假设为ASCII码,那么用int数组记录第一个字串出现的次数,当第二字符串字符出现次数大于第一个出现次数时,返回false。第一个字符串使用toCharArray的方法,第二个使用charAt()。
1.4编写方法,将字符串中的空格全部替换为“%20”。
思路:字符串的操作,这个要进行两次遍历,先进行一次得到数组中空字符串的个数,然后再进行处理。从尾部开始,避免数据丢失。
这里给出代码:
public void replaceSpace(char[] str, int length){
int spacenum = 0, newlength, i;
for(i = 0; i < length; i++){
if (str[i] == ' '){
spacenum++;
}
}
newlength = length + spacenum * 2;
str[newlength] = '\n';//结束标志位
int index = newlength - 1;
for (i = length - 1; i > 0; i--){
if (str[i] != ' '){
str[index--] = str[i];
} else {
str[index--] = '0';
str[index--] = '2';
str[index--] = '%';
}
}
}
1.5利用字符重复出现的次数,编写一个方法,实现基本的字符串的压缩功能。比如,字符串“aabcccccaaa”会变为a2b1c5a3。若压缩后没有变短,就返回原字符串。
思路:采用StringBuffer,按照规则进行统计,最后进行长度统计。
1.6给定一幅N*N矩阵表示的图像,其中每个像素的大小为4个字节,编写一个方法,将图像旋转90度。不占用额外的空间。
思路:进行一层层的旋转,对每一层进行环状旋转。用first来定义每一个环的最小下标,last表示最小标志,offset做缓存。
public static void rotate(int[][] num) {
int len = num.length;
for (int layer = 0; layer < len / 2; layer++){
int first = layer;
int last = len - 1 - first;
for (int i = first; i < last; i++){
int offset = i - first;
int top = num[first][i];
//左转上
num[first][i] = num[last - offset][first];
//下转左
num[last - offset][first] = num[last][last - offset];
//右转下
num[last][last - offset] = num[i][last];
//上转右
num[i][last] = top;
}
}
}
1.7编写一个方法,若M*N矩阵中某个元素为0,则将其所在的行与列清零。
思路:这是一个陷阱题,首先用两个booelan类型的数组进行归零行以及列的标记,下一步再进行清零操作。
1.8假定有一个方法isSubString,给定两个字符串s1与s2,编写代码检查s2是否由s1旋转组成,只能调用一次isSubString的方法。
思路:out of box方法,假定s1 = xy = waterbottle,x = wat, y = terbottle,s2 = yx =erbottlemat,yx 肯定是xyxy的子串。先进行长度的判定,长度不同,肯定返回false,然后利用子串进行判定。
public boolean isRotation(String s1, String s2) {
if (s1 == null && s2 == null) return false;
if (s1 == null || s2 == null) return true;
int len = s1.length();
if (len == s2.length() && len > 0) {
String s1s1 = s1 + s1;
return isSubstring(s1s1, s1);
}
return false;
}
链表
链表这块需要注意dummy的使用
2.1 编写代码,移除未排序链表中的重复节点。进阶:如果不使用缓冲区,该怎么解决?
思路:利用散列表记录,有一个函数需要掌握,containsKey(),同时注意链表的删除操作,要有previous的缓存。
public void deleteDups(LinkedListNode n){
Hashtable table = new Hashtable();
LinkedListNode previous = null;
while (n != null){
if (table.containsKey(n.data)){
previous.next = n.next;
} else {
table.put(n.data, true);
previous = n;
}
n = n.next;
}
}
不使用缓存区,就得采用两个指针进行迭代处理
2.2实现一个算法,找出单向链表中倒数第k个结点。
思路:用先行指针的方法
2.3实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。
思路:将当前结点的下一个结点的值复制到当前结点,然后删除下一个结点。
2.4编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。
思路:这个是采用新建两个链表,然后进行合并处理的方式。
java中的super()方法是针对父类来说的。
2.5给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的也就是个位排在链表的首部。编写函数对这两个整数求和,并利用链表形式返回结果。
示例 : 输入 (7-> 1 -> 6) + ( 5 -> 9 -> 2) ,即617 + 295 与普通的加法保持一致 输出 2-> 1-> 9 ,即912.
这个采用的是普通的加法进行处理。
进阶 :假设这些数位是正向存放的,请再做一遍。
这个是要用到递归进行处理,需要注意两个数的位数不相等。
2.6给定一个有环链表,实现一个算法返回环路的开头结点。
有环链表的定义,在链表中的某个结点的next元素指向在它前面出现过的结点,则表明链表存在环路。
示例
输入: A->B ->C->D->E->C (C结点出现了两次)
那么返回:C 结点
思路:设置两个指针,fast与slow,假设从第s个结点,进入到环路,那么当slow在s结点的时候,fast处于2s结点,让大S为 S = s mod loop_size,此时,fast与slow相差S步,在loop_size - S 步以后,fast 与 slow相遇,碰撞结点距离环路开始的位置为S,(对于slow来说,走了loop_size - S步,那么loop_size- (loop_size- S))此时碰撞结点距离环路起始处的距离为s个结点,链表开始位置也是距离起始处s个结点,那么两个指针方向同时出发,会相交于环路起始结点。
public static LinkedListNode getBegainHead(LinkedListNode head){
LinkedListNode fast = head;
LinkedListNode slow = head;
while (fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if (fast == slow){
break;
}
}
if (fast == null || fast.next == null){
return null;
}
slow = head;
while (slow != fast){
slow = slow.next;
fast = fast.next;
}
return fast;
}
这是一个很经典的方法。
2.7 编写一个函数,检查链表是否为回文。
思路:先行指针,取到中间位置的数,用stack来存储数据,注意长度为奇数,根据fast指向的内容即可判断。
栈与队列
3.2请设计一个栈,除push与pop方法,还支持min方法,可返回栈元素中的最小值。push、pop、min方法的时间复杂度为O(1)。
思路:1.新封装 一个对象,对象中包含 value 以及 min,在stack中进行存储。注意super.调用父类方法,peek()查看栈顶元素
2.与以上类似,设计子类,继承Stack,子类中增加新的Stack用于存储出现变化的最小值,入栈操作时进行比较,出栈操作时进行更新。
3.4经典汉诺塔问题
思路:利用stack的数据类型做数据存储
递归方法求解:
public void moveDisks(int n, Tower origin, Tower destination, Tower buffer){
if (n <= 0) return;
moveDisks(n -1, origin, buffer, destination);
moveTop(origin, destination);
moveDisks(n - 1, buffer, destination, origin);
}
3.5实现一个MyQuene类,该类用两个栈来实现一个队列
思路:两个栈,一个为new,另外一个为old。队列add数据为new push数据,队列出数据,从old pop数据,如果old 为空,则将new出栈,old入栈。
两个队列实现栈的话,
class MyStack {
private Queue<Integer> inquene = new LinkedList<Integer>();
private Queue<Integer> outquene = new LinkedList<Integer>();
int temp =0;
// Push element x onto stack.
public void push(int x) {
inquene.add(x);
}
// Removes the element on top of the stack.
public void pop() {
if ( inquene.isEmpty()){
while (!outquene.isEmpty()){
temp = outquene.peek();
outquene.poll();
if( !outquene.isEmpty() ){
inquene.add(temp);
}
}
} else {
while (!inquene.isEmpty()){
temp = inquene.peek();
inquene.poll();
if( !inquene.isEmpty() ){
outquene.add(temp);
}
}
}
}
// Get the top element.
public int top() {
if( inquene.isEmpty() ){
while( !outquene.isEmpty() ){
temp = outquene.peek();
outquene.poll();
}
return temp;
}else{
while( !inquene.isEmpty() ){
temp = inquene.peek();
inquene.poll();
outquene.add(temp);
}
return temp;
}
}
// Return whether the stack is empty.
public boolean empty() {
return inquene.isEmpty()&&outquene.isEmpty();
}
}
3.6经典的stack排序问题,按照升序对栈进行排序。最多只能使用一个额外的栈存放临时数据。
思路:新建一个栈,
//注意给定两个while指针的使用
public Stack<Integer> stackSort(Stack<Integer> s1){
Stack<Integer> s2 = new Stack<Integer>();
while (!s1.isEmpty()){
Integer temp = s1.pop();
while(!s2.isEmpty() && s2.peek() > temp){// 本次while循环保证temp是当前s2里面最小的那个
s1.push(s2.pop());
}
s2.push(temp);//
}
return s2;
}
树与图
树的三种遍历方式,前序遍历,中序遍历,后序遍历等,递归形式的实现以及使用栈与队列迭代形式的实现。用V表示父节点,L表示左子节点,R表示右子节点,则VLR表示先序遍历,LVR表示中序遍历,LRV表示后序遍历。在处理时,按照递归的思想,对每一个结点都进行顺序上的检查处理。前序,中序,后序是根据中间结点被访问的相对顺序来说的。
判断图是否有环:
针对无向图可以采用并查集来判断是否有环:
class GraphCycle{
int V, E; // V-> no. of vertices & E->no.of edges
Edge edge[]; // /collection of all edges
class Edge {//edges
int src, dest;
};
// Creates a graph with V vertices and E edges
GraphCycle(int v,int e){
V = v;
E = e;
edge = new Edge[E];
for (int i=0; i<e; ++i)
edge[i] = new Edge();
}
// A utility function to find the subset of an element i
int find(int parent[], int i){
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
// A utility function to do union of two subsets
void Union(int parent[], int x, int y){
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
// The main function to check whether a given graph
// contains cycle or not
int isCycle( GraphCycle graph) {
// Allocate memory for creating V subsets
int parent[] = new int[graph.V];
// Initialize all subsets as single element sets
for (int i=0; i<graph.V; ++i)
parent[i]=-1;
// Iterate through all edges of graph, find subset of both
// vertices of every edge, if both subsets are same, then
// there is cycle in graph.
for (int i = 0; i < graph.E; ++i) {
int x = graph.find(parent, graph.edge[i].src);
int y = graph.find(parent, graph.edge[i].dest);
if (x == y && x != -1)
return 1;
graph.Union(parent, x, y);
}
return 0;
}
针对有向图,采用的是dfs加上标记位来做
public boolean canFinish(int numCourses, int[][] prerequisites) {
HashSet<Integer>[] edges = new HashSet[numCourses];
for (int i = 0 ; i < numCourses; i++) {
edges[i] = new HashSet<Integer>();
}
for (int[] temp : prerequisites) {
HashSet a = edges[temp[0]];
a.add(temp[1]);
}
for (int i = 0; i < numCourses; i++) {
boolean[] visit = new boolean[numCourses];
boolean[] resTack = new boolean[numCourses];
Arrays.fill(visit, false);
Arrays.fill(resTack, false);
if (isRecyle(i, edges, visit, resTack)) {
return false;
}
}
return true;
}
public boolean isRecyle(int i,HashSet<Integer>[] edges, boolean[] visit, boolean[] resTack) {
if (visit[i] == false) {
visit[i] = true;
resTack[i] = true;
HashSet<Integer> pre = edges[i];
for (int id : pre) {
if (! visit[id] && isRecyle(id,edges, visit, resTack) ) {
return true;
} else if (resTack[id]) {
return true;
}
}
}
resTack[i] = false;
return false;
}
图的遍历
深度优先搜索
// 递归版本
void DFSsearch(Node root){
if (root == null){
return;
}
visit(root);
root.isvisited = true;
foreach (Node n in root.adjacent){
search(n);
}
}
//迭代版本,与树的前序遍历类似,先访问本节点的内容,右边入栈,左边入栈
void DFSearch(Node root){
if (root == null){
return;
}
Stack<Node> stack = new Stack(root);
stack.push(root);
Node temp = null;
while(stack.size() > 0){
temp = stack.pop();
if (root.right != null){
stack.push(root.right)
}
if (root.left != null){
stack.push(root.left);
}
}
}
//广度优先的话,就采用队列的形式
void search(Node root){
if (null == root){
return;
}
Quene<Node> que = new LinkedList<Node>();
root.visited = true;
visit(root);
que.add(root);
Node temp = null;
while (que.size() > 0){
temp = que.remove();
for (Node n in temp.adjacent){
visit(n);
n.visited = true;
que.add(n);
}
}
}
注意substring(a, b);从0开始,包含a,但是不包含b。
递归版树的遍历
前序vlr
public void trvelPre(TreeNode root){
if (null == root){
retrun;
}
visit(root.data);
trvelPre(root.left);
trvelPre(root.right);
}
中序lvr以及后续lrv只是修改递归的后三条语句就可以
迭代版的遍历
前序遍历
public void trvelPre(TreeNode root){
if (null == root){
return;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode temp = stack.pop();
visit(temp);
if (null != temp.right){
stack.push(temp.right);
}
if (null != temp.left){
stack.push(temp.left);
}
}
}
中序遍历,对于完备的树,可以得到parent,空间复杂度为O(1),非完备的就先不这样处理
public void trvelIn(TreeNode root){
if (null == root){return;}
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root;
//中序遍历方式有所不同,碰到左节点就入栈,左节点访问完成后,转向右子节点
while (true){
if (temp != null){
stack.push(temp);
temp = temp.left;
} else if (!stack.isEmpty()){
temp = stack.pop();
System.out.print(temp.val);
temp = temp.right;
} else {
break;
}
}
}
后序遍历
public void vistiafter(TreeNode root) {
if (null == root ){return;}
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root;
stack.push(temp);
while (!stack.isEmpty()){
if ((stack.peek().left != temp) && (stack.peek().right != temp)){
//当前temp结点既不表示栈顶元素的左节点也不表示右节点,
//temp栈顶元素的左兄弟结点或者是root结点
// 表示切换子树
getHLVFL(stack.peek(), stack);
}
temp = stack.pop();
System.out.print(temp.val);
}
}
public void getHLVFL(TreeNode temp, Stack<TreeNode> stack){
while(null != temp){
if (null != temp.left){
if (null != temp.right){
stack.push(temp.right);
}
stack.push(temp.left);
} else {
stack.push(temp.right);
}
temp = stack.peek();
}
stack.pop();
}
后序遍历结束
根据vlr以及lvr,得到lrv,构造出树
public TreeNode getaf(String pre, String in ,int len){
TreeNode node = new TreeNode();
if(len == 0){
return null;
}
char root = pre.charAt(0);
int index = 0;
for(int i = 0; i < len; i++){
if(root == in.charAt(i)){
index = i;
break;
}
}
node.val = root;
node.left = getaf(pre.substring(1,index + 1), in.substring(0, index), index );
node.right = getaf(pre.substring(index + 1, len), in.substring(index + 1, len), len - index - 1);
System.out.println(root);//这条语句放的位置,可以输出不同的遍历顺序,现在是后序,最上面是前序,放到中间就是中序
return node;
}
//root.right = bulidHelp(pre, in, preroot + index - left + 1, index + 1, right);对两边的进行处理
图的DFS深度优先遍历,BFS广度优先遍历,要使用到队列
4.1检查二叉树是否平衡,任意一个结点,其两棵子树的高度差不超过1
思路:递归得到树的高度,递归判断每一个结点是否平衡
public int checkHeight(TreeNode root){
if (null == root){
return 0;
}
int leftHeight = checkHeight(root.left);
if (leftHeight == -1){
return -1;
}
int rightHeight = cheakHeight(root.right);
if (rightHeight == -1){
return -1;
}
if (Math.abs(leftHeight - rightHeight) > 1){
return -1;
} else {
return Math.max(leftHeight, rightHeight) + 1;
}
}
public boolean isBlance(TreeNode tree){
if (checkHeight(tree) == -1){
return fasle;
} else {
return true;
}
}
4.2给定一个有向图,找出两个节点之间是否存在一条路径
广度优先搜索适合查找最短路径
public enum State{
Unvisited, Visited, Visiting;
}
public static boolean search(Graph g, Node start, Node end){
if (null == g || null == start){
return false;
}
LinkedList<Node> list = new LinkedList<Node>();
for (Node u : g.getNodes()){
u.state = State.Unvisited;
}
list.add(start);
start.state = State.Visiting;
Node u = null;
while(!list.isEmpty()){
u = q.removeFirst();
if (u != null){
for (Node v : u.getAdjacent()){
if (v.state == State.Unvisited){
if (v == end){
return true;
} else {
v.state = State.Visiting;
q.add(v);
}
}
}
u.state = State.Visited;
}
}
return false;
}
4.3给定一个有序整数数组,元素个不相同,并且按照升序排列,编写算法创建高度最小的二叉查找树。
使处于中间位置上的元素更靠近根节点。
public TreeNode createMin(int nums, int start, int end){
if (start > end){
return null;
}
int mid = start + ((end - start) >> 1);
TreeNode temp = new TreeNode(nums[mid]);
temp.left = createMin(nums, start, mid - 1);
temp.right = createMin(nums, mid + 1, end);
return temp;
}
public TreeNode createMinBFS(int[] nums){
return createMin(nums, 0, nums.length - 1);
}
4.4给定一个二叉树,创建含有某一深度上的所有结点的链表,比如一棵树的深度为D,创建D个链表。
思路:对广度优先的算法进行稍微修改,一层一层增加即可。
4.5检查一课二叉树是否为二叉查找树
思路:1.如果不存在相等的树,所有左结点小于等于当前结点,当前结点小于右结点,current.left.val<= current.val < current.right.val(程序也可以这么写)。改进,递归
public static int last_printed = integer.MIN_VALUE;
public boolean checkBFS(TreeNode root){
if (null == root){return true;}
if (!checkBFS(root.left)){
return false;
}
if (root.val <= last_printed){// 右侧的必须大于
return false;
}
last_printed = temp.val;
if (!checkBFS(root.right)){
return false;
}
return true;
}
思路2,利用最大最小算法,递归,限定当前结点的数的范围,与数学上的夹逼准则类似
public boolean checkMaxMinBFS(TreeNode root, int min, int max){
if (null == root){return true;}
if (root.val <= min || root.val > max){
return false;
}
if (!checkMaxMinBFS(root.left, min, root.val) || !checkMaxMinBFS(root.right, root.val, max)){
return false;
}
return true;
}
public boolean checkBFS(TreeNode root){
return checkMaxMinBFS(root, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
#
4.6找出二叉查找树的下一个结点(可以得到父节点)
思路:也就是中序遍历的下一个结点,此时要分开讨论
public TreeNode leftMostChild(TreeNode root){
if (null == root){return null;}
while(root.left != null){
root = root.left;
}
return root;
}
public TreeNode inorderSucc(TreeNode n){
if (null == n){return null;}
if(n.right != null){
return leftMostChild(n.right);
} else {
TreeNode cu = n;
TreeNode pa = cu.parent;
while (pa != null && pa.left != cu){
cu = pa;
pa = cu.parent;
}
return pa;
}
}
4.7 找出二叉树中两个结点的第一个共同祖先。不得将额外的结点存储在数据结构中
思路:1.从根结点调用cover方法,先检查是否都在根结点中,不在的话返回null,然后依次调用,left与right,
2.递归,优化常数时间 返回值依次为 返回p,root子树包含p不包含q;返回q,root子树中包含q不包含p;返回null,都不包含;否则返回 p与q的共同祖先。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left != null && right != null) return root;
return left != null ? left : right;
}
// 非递归版本的,时间复杂度挺高的。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
Map<TreeNode, TreeNode> parents = new HashMap<TreeNode, TreeNode>();
Queue<TreeNode > queue = new LinkedList<>();
parents.put(root, null);
queue.offer(root);
TreeNode temp = null;
while (!parents.containsKey(p) || !parents.containsKey(q)) {
temp = queue.poll();
if (temp.left != null) {
parents.put(temp.left, temp);
queue.offer(temp.left);
}
if (temp.right != null) {
parents.put(temp.right, temp);
queue.offer(temp.right);
}
}
Set<TreeNode> anc = new HashSet<>();
while (p != null) {
anc.add(p);
p = parents.get(p);
}
while (!anc.contains(q)) {
q = parents.get(q);
}
return q;
}
4.8你有一颗非常大的二叉树:T1,有几百万个结点;T2有几百个结点。设计一个算法,判断T2是不是T1的子树
思路1:对两个树前序以及中序遍历,判断T2是不是T1的子串,注意null补全特殊的字符
2:递归调用
public boolean treeMatch(TreeNode a, TreeNode b){
if (a == null && b == null){
return true;
}
if (a == null || b == null){
return false;
}
if (a.val != b.val){
return false;
}
return (treeMatch(a.left, b.left) && treeMatch(a.right, b.right));
}
public boolean isSubTree(TreeNode t1, TreeNode t2){
if (null == t1 && null == t2){
return true;
}
if (t1 == null){
return false;
}
if (t1.val == t2.val){
if (treeMatch(t1, t2)){return true;}
}
return (isSubTree(t1.left, t2) || isSubTree(t1.right, t2));
}
4.9给定一个二叉树,其中每一个结点都含有一个数值。设计一个算法,打印某个结点数值总和等于某个给定值的所有路径。
思路:从根节点出发进行计算