目录
1 数组与矩阵
1.1 第一个只出现一次的字符位置
1.2 二维数组中的查找
给定一个二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。
解题思路:
该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来快速地缩小查找区间,每次减少一行或者一列的元素。当前元素的查找区间为左下角的所有元素。
public static bool Find(int target,int[,] nums)
{
if (nums == null || nums.Length == 0 || nums.GetLength(0) == 0)
return false;
int row = nums.GetLength(1), col = nums.GetLength(0);
int i = 0, j = col - 1;
while (i <= row -1 && j >= 0){
if (target == nums[i,j])
return true;
else if (target > nums[i,j])
i++;
else
j--;
}
return false;
}
1.3 数组中重复的数字
static public int duplicate(int[] nums)
{
for(int i = 0; i < nums.Length; i++)
{
while(nums[i] != i)
{
if (nums[i] == nums[nums[i]])
{
return nums[i];
}
swap(nums, i, nums[i]);
}
}
return -1;
}
static private void swap(int[] nums,int i,int j)
{
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
1.4 顺时针打印矩阵
按顺时针的方向,从外到里打印矩阵的值。
* 下图的矩阵打印结果为:
* 1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10
解题思路:
* 一层一层从外到里打印,观察可知每一层打印都有相同的处理步骤,
* 唯一不同的是上下左右的边界不同了。
* 因此使用四个变量 r1, r2, c1, c2 分别存储上下左右边界值,
* 从而定义当前最外层。
* 打印当前最外层的顺序:
* 从左到右打印最上一行->从上到下打印最右一行
* ->从右到左打印最下一行->从下到上打印最左一行。
public static void PrintInOrder(int[,] arr)
{
int c1 = 0, c2 = arr.GetLength(0)-1;
int r1 = 0, r2 = arr.GetLength(1)-1;
while (c1 <= c2 || r1 <= r2)
{
for (int j = c1; j < c2; j++)
Print(arr[r1, j]);
for (int i = r1; i < r2; i++)
Print(arr[i, c2]);
for (int j = c2; j >c1; j--)
Print(arr[r2, j]);
for (int i = r2; i >= r1+1; i--)
Print(arr[i, c1]);
c1++;c2--;r1++;r2--;
if (c1 == c2 && r1 == r2)
Console.WriteLine(arr[r1, c1]);
}
}
private static void Print(int a)
{
Console.Write(a+",");
}
1.5 替换空格
将一个字符串中的空格替换成 "%20"。
解题思路:
数出原数组中出现了多少次空格,然后新声明一个数组,将其容量改为原数组容量加上空格数*3,再依次判断将原数组的元素和空格出现时要替换的字符存入新数组。
public static char[] ReplaceSpace(char[] arr1)
{
//记录原数组有多少个空格
int count = 0;
for (int n=0; n < arr1.Length; n++)
{
if(arr1[n] == ' ')
{
count++;
}
}
//重新定义一个数组,用来存放替换空格后的结果
char[] arr2=new char[arr1.Length+count*3];
//i用来记录arr2下标
//j用来记录arr1下标
for (int i = 0, j = 0; i < arr2.Length && j<arr1.Length;i++,j++)
{
if(arr1[j] != ' ')//如果arr1此时的元素不是空格,则直接存入新的数组
{
arr2[i] = arr1[j];
}
else
{
//遍历到arr1中空格的位置时
//arr2从当前位置开始往后的依次存入%20
arr2[i]='%';
arr2[++i] = '2';
arr2[++i] = '0';
}
}
return arr2;
}
2 栈队列堆
2.1包含 min 函数的栈
实现一个包含 min() 函数的栈,该方法返回当前栈中最小的值。
解题思路
使用一个额外的 minStack,栈顶元素为当前栈中最小的值。在对栈进行 push 入栈和 pop 出栈操作时,同样需要对 minStack 进行入栈出栈操作,从而使 minStack 栈顶元素一直为当前栈中最小的值。在进行 push 操作时,需要比较入栈元素和当前栈中最小值,将值较小的元素 push 到 minStack 中。
//定义一个类来实现要求的栈
public class MinStack
{
Stack<int> data; //定义一个栈来存输入的数据
Stack<int> min; //定义一个栈来专门存放最小值
public MinStack()
{
data = new Stack<int>();
min = new Stack<int>();
}
public void Push(int value)//入栈
{
data.Push(value);
//如果当前栈中没有元素,就默认入栈的第一个数字是最小值,存入min栈中
//当前栈中有元素,则比较min栈中存的值是否大于等于入栈的值,是的话直接存入
//多次的入栈操作只会让min栈的栈顶元素越来越小
if(min.Count == 0||min.Peek()>=value)
min.Push(value);
}
public void Pop() //出栈
{
if (data.Count == 0)
return;
//如果出栈的这个数字比min栈栈顶的值还小,则min栈也出栈
//(一般只会等于不会大于
int t=data.Pop();
if(min.Peek()>=t)
min.Pop();
}
public void minPeek() //检查总栈中的最小值
{
if (min.Count > 0)
Console.WriteLine(min.Peek());
}
2.2 数据流中的中位数
如何得到一个数据流中的中位数
解题思路
对数组进行排序,再根据数组长度和奇偶取中位数。
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值;
如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
public static void MidCount(int[] arr)
{
int[] arr2 = MaoPao(arr);//冒泡排序和插入排序都可以用
float result = 0;
float len = arr2.Length;
if (len % 2 == 0)
result = (len - 1) / 2 + len % 2;
else
result = arr2[arr2.Length / 2];
Console.WriteLine(result);
}
//冒泡排序
public static int[] MaoPao(int[] arr)
{
int[] arr2 = new int[arr.Length];
for (int n = 0; n < arr.Length; n++)
{
arr2[n] = arr[n];
}
for (int i = 0; i < arr2.Length - 1; i++)
{
for (int j = 0; j < arr2.Length - 1 - i; j++)
{
if (arr2[j] > arr2[j + 1])
{
int temp = arr2[j];
arr2[j] = arr2[j + 1];
arr2[j + 1] = temp;
}
}
}
return arr2;
}
//插入排序
public static int[] ChaRu(int[] arr)
{
int[] arr2 = new int[arr.Length];
for (int n = 0; n < arr.Length; n++)
{
arr2[n] = arr[n];
}
for (int i = 1; i < arr2.Length; i++)
{
int t = i;
while (t - 1 >= 0 && arr2[t - 1] > arr2[t] )
{
int temp=arr2[t];
arr2[t] = arr2[t - 1];
arr2[t-1]=temp;
t--;
}
}
for (int n = 0; n < arr2.Length; n++)
{
Console.WriteLine(arr2[n]);
}
return arr2;
}
2.3 最小的 K 个数
2.4 用两个栈实现队列
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。
解题思路
数据存放栈stack1用来处理入栈(push)操作,临时栈stack2用来处理出栈(pop)操作。一个元素进入stack1栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入stack2栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。
//定义一个类来完成两个栈实现队列
public class XQUeue
{
private Stack<int> stack1;//数据存放栈
private Stack<int> stack2;//数据输出时的临时栈
public XQUeue()
{
stack1 = new Stack<int>();
stack2 = new Stack<int>();
}
public void inQueue(int value)//入栈操作
{
stack1.Push(value);//直接压入数据存放栈
}
public void outQueue()//出栈操作
{
int value = 0;
//为了达到队列的“先进先出”,把数据储存栈顺序反转,取出栈底数据
while(stack1.Count > 0)
{
value = stack1.Pop();
stack2.Push(value);//暂时放在临时栈
}
int t = stack2.Pop();
Console.WriteLine(t);
//再把栈反转一次,恢复原来的顺序
while (stack2.Count > 0)
stack1.Push(stack2.Pop());
}
2.5 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
eg:假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。
解题思路
使用一个栈来模拟压入弹出操作。
每次入栈一个元素后,都要判断一下栈顶元素是不是当前出栈序列popSequence 的第一个元素,如果是的话则执行出栈操作并将popSequence 往后移一位,继续进行判断。
public static bool popSequence(int[] arr1, int[] arr2)
{
//声明一个栈来模拟入栈操作
Stack<int> stack = new Stack<int>();
int t = 0; //记录arr2的下标
int count = 0;//记录栈中元素个数(用来判断是否栈空
for (int i = 0; i < arr1.Length; i++)
{
int temp=arr1[i];
stack.Push(temp);
count++;
while (count>0)
{
int value=stack.Peek();
//如果栈顶元素和判断出栈数组的元素相等
//则按照其进行出栈操作
if (value == arr2[t])
{
stack.Pop();
t++; //判断出栈数组的下一个元素
count--;
}
else
break;
}
}
//如果循环完过后,判断出栈数组的元素已经全部遍历完,则是当前出栈顺序
if (t == arr2.Length)
return true;
else
return false;
}
2.6 字符流中第一个不重复的字符
2.7 滑动窗口的最大值
3 链表
3.1 从尾到头打印链表
从尾到头反过来打印出每个结点的值。
解题思路:
栈具有后进先出的特点,在遍历链表时将值按顺序放入栈中,最后出栈的顺序即为逆序。
public class Node<T>//简答做一个单向链表
{
public T value;
public Node<T> nextNode;
public Node(T value)
{
this.value = value;
}
}
public static void printListFromTailToHead(Node<int> head)
{
Stack<int> stack = new Stack<int>();
//在遍历单向链表的同时,存入栈中
while (head != null)
{
int v=head.value;
stack.Push(v);
head=head.nextNode;
}
//利用栈的先进先出的特点,达到反转打印的目的
while(stack.Count > 0)
{
int t=stack.Pop();
Console.Write(t);
}
}
3.2 合并两个有序链表
解题思路:
直接迭代比较,谁更小就连上谁。
public static Node<int> Merge(Node<int> n1, Node<int> n2)
{
//创建头节点来返回结果链表
Node<int> head=new Node<int>(-1);
//创建一个临时指针来遍历链表
Node<int> cur = head;
//遍历链表
while (n1 != null && n2 != null)
{
//循环比较,哪个链表当前值更小就连上哪边
if (n1.value <= n2.value)
{
cur.nextNode = n1;
n1 = n1.nextNode;
}
else
{
cur.nextNode = n2;
n2 = n2.nextNode;
}
//临时指针移到已经确定的位置
cur=cur.nextNode;
}
if (n1 != null)
cur.nextNode = n1;
if (n2 != null)
cur.nextNode = n2;
return head.nextNode;
}
3.3 删除链表中重复的节点
在一个有序链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。例如链表 1->2->3->3->4->4->5 ,处理后为 1->2->5。
解题思路:
迭代判断是否出现重复部分,如果没有就连上新的链表,有则直接跳过。
public static Node<int> RemoveRepeat(Node<int> n)
{
Node<int> head = new Node<int>(-1);
Node<int> cur=head;
int temp = 0;
while (n != null && n.nextNode != null)
{
if (n.value != n.nextNode.value)
{
cur.nextNode = n;
cur = cur.nextNode;
n = n.nextNode;
continue;
}
//如果没有到最后一个节点且当前节点的值与下一节点的相同
while(n.nextNode!=null && n.value == n.nextNode.value)
{
temp=n.value;
n = n.nextNode;
if (n.nextNode != null && n.nextNode.value == temp )
continue;
else
{
n = n.nextNode;
cur.nextNode=n;//跳过前面重复的节点
break;
}
}
}
return head.nextNode;
}
3.4 反转链表
给定一个单链表的头结点pHead(该头节点值是1),长度为n,反转该链表后,返回新链表的表头。如当输入链表{1,2,3}时,经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
解题思路
栈具有后进先出的特点,在遍历链表时将值按顺序放入栈中,最后出栈的顺序即为逆序。
public static Node<int> ReverseList(Node<int> n)
{
Node<int> cur = n;
Stack<int> stack=new Stack<int>();
//利用栈先进先出的特点来反转存放链表值
while(cur!= null)
{
stack.Push(cur.value);
cur=cur.nextNode;
}
//声明一个新的链表来存放反转后的值
Node<int> newNode=new Node<int>(-1);
cur=newNode;
int temp = 0;
while (stack.Count != 0)
{
temp=stack.Pop();
Node<int> tNode = new Node<int>(temp);
cur.nextNode = tNode;
cur=cur.nextNode;
}
return newNode.nextNode;
}
3.5 两个链表的第一个公共节点
如图,找到两个链表的第一个公共节点c1。
解题思路
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。当访问链表 A 的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B;同样地,当访问链表 B 的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
public static Node<int> FindFirstCommonNode(Node<int> a, Node<int> b)
{
//创建两个临时指针来分别遍历两个链表
Node<int> p1 = a;
Node<int> p2 = b;
int count = 0; //用来记录完整遍历了几次
while (p1 != p2)
{
//如果两个链表都遍历过一遍但是仍没找到公共节点
//就说明这两个链表没有公共节点
if (p1.value == p2.value||count>2)
break;
else
{
p1 = p1.nextNode;
p2 = p2.nextNode;
}
//如果哪边的链表已经遍历完了,就从另一个表头重新开始遍历
if (p1 == null)
{
p1 = b;
count++;
}
else if (p2 == null)
{
p2 = a;
count++;
}
}
if (count > 2)//没有公共节点的情况
{
Console.WriteLine("-1");
return null;
}
else
return p2;
}