Datawhale 数据结构与算法
1数组
- 数组是一种常用的数据结构
- 可以看作是线性表的推广
- 数据元素多样,但必须属于同一数据类型
1.1 逻辑结构
数组是n(N>=1)个相同数据类型的数据元素的有限序列;
数组是具有固定格式和数量的数据有序集;
注意:在数据上不能进行插入、删除数据元素等操作
数组的操作:
- 取值:读取给定一组下标对应的数据元素
- 赋值:存储或修改给定一组下标对应的数据元素
- 清空
- 复制
- 排序:数据元素排序(元素为可排序)
- 反转:反转元素顺序
1.2 内存映像
数组是一种随机存储结构
有两种存储方法是:
- 以行为主序
- 以列为主序
1.3 C#中的数组
C#支持一维数组、多维数组、交错数组
数组在托管堆上分配空间,是引用类型;
常用方法
public abstract class Array : ICloneable, IList, ICollection, IEnumerable
{
public bool IsFixedSize{get;}
public iint Length{get;}
//获取 Array 的秩(维数)。
public int Rank { get; }
//实现的 IComparable 接口,在.Array 中搜索特定元素。
public static int BinarySearch(Array array, object value);
//实现的 IComparable<T>泛型接口,在 Array 中搜索特定元素。
public static int BinarySearch<T>(T[] array, T value);
//实现 IComparable 接口,在 Array 的某个范围中搜索值。
public static int BinarySearch(Array array, int index, int length,object value);
//实现的 IComparable<T>泛型接口,在 Array 中搜索值。
public static int BinarySearch<T>(T[] array, int index, int length, T value);
//Array 设置为零、false 或 null,具体取决于元素类型。
public static void Clear(Array array, int index, int length);
//System.Array 的浅表副本。
public object Clone();
//从第一个元素开始复制 Array 中的一系列元素 //到另一 Array 中(从第一个元素开始)。
public static void Copy(Array sourceArray, Array destinationArray, int length);
//将一维 Array 的所有元素复制到指定的一维 Array 中。
public void CopyTo(Array array, int index);
//创建使用从零开始的索引、具有指定 Type 和维长的多维 Array。
public static Array CreateInstance(Type elementType, params int[] lengths);
//返回 ArrayIEnumerator。
public IEnumerator GetEnumerator();
//获取 Array 指定维中的元素数。
public int GetLength(int dimension);
//获取一维 Array 中指定位置的值。
public object GetValue(int index);
//返回整个一维 Array 中第一个匹配项的索引。
public static int IndexOf(Array array, object value);
//返回整个.Array 中第一个匹配项的索引。
public static int IndexOf<T>(T[] array, T value);
//返回整个一维 Array 中后一个匹配项的索引。
public static int LastIndexOf(Array array, object value);
//反转整个一维 Array 中元素的顺序。
public static void Reverse(Array array);
//设置给一维 Array 中指定位置的元素。
public void SetValue(object value, int index);
//对整个一维 Array 中的元素进行排序。
public static void Sort(Array array);
}
1.4 练习部分
练习一:利用动态数组解决数据存放问题
编写一段代码,要求输入一个整数N,用动态数组A来存放2~N之间所有5或7的倍数,输出该数组。
C#代码
static void Main(string[] args)
{
Console.WriteLine("N=");
string str = Console.ReadLine();
int n=0;
if (int.TryParse(str, out n))
{
DArray<int> dArr = new DArray<int>();
int j = 0;
for (int i = 2; i <= n; i++)
{
if (i%5 == 0 || i%7 == 0)
{
dArr.Append(i);
}
}
Console.Write(dArr);
}
}
练习二:托普利茨矩阵问题
https://leetcode-cn.com/problems/toeplitz-matrix/
如果一个矩阵的每一方向由左上到右下的对角线上具有相同元素,那么这个矩阵是托普利茨矩阵。
给定一个M x N的矩阵,当且仅当它是托普利茨矩阵时返回True。
public class Solution {
public bool IsToeplitzMatrix(int[][] matrix)
{
for(int i = 0; i < matrix.Length - 1; i++)
{
for(int j = 0; j < matrix[i].Length - 1; j++)
{
if(matrix[i][j] != matrix[i+1][j+1])
{
return false;
}
}
}
return true;
}
}
练习三:三数之和
https://leetcode-cn.com/problems/3sum/
给定一个包含 n 个整数的数组nums
,判断nums
中是否存在三个元素a,b,c
,使得a + b + c = 0
?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
public class Solution {
public IList<IList<int>> ThreeSum(int[] nums) {
Array.Sort(nums);
IList<IList<int>> all = new List<IList<int>>();
int len = nums.Length;
int x = 0, y, z;
List<int> li;
for (; x < len-1; x++)
{
if (nums[x] > 0)
break;
if (x > 0 && nums[x] == nums[x - 1])
continue;
z = x + 1;
y=len - 1;
while (z < y )
{
int num = nums[x] + nums[y] + nums[z];
if (num > 0)
while (y>z && nums[y] == nums[--y]) ;
else if (num < 0)
while ( y>z && nums[z] == nums[++z]) ;
else
{
li = new List<int>();
li.Add(nums[x]);
li.Add(nums[z]);
li.Add(nums[y]);
all.Add(li);
while(y>x && nums[y]==nums[--y]);
}
}
}
return all;
}
}
2顺序表和链表
2.1 线性表
- 线性表是最简单、最基本、最常用的数据结构;
- 特定是数据元素之间存在一对一的线性关系;
- 除开始、结束数据元素外,其他数据元素都有且仅有一个直接前驱和直接后续;
- 两种存储结构:顺序存储和链式存储
2.2 顺序表
顺序存储的线性表叫顺序表,
表中存储单元连续,C#中用数组来实现
2.3 链表
链式存储的线性表是链表,存储单元不一定连续
在一个节点中,还有数据域存放数据元素本身信息,还有引用域存储其相邻的数据元素的地址信息。
- 单链表:只有一个引用域,村方其后直接后续节点的地址信息
- 双向链表:有两个引用域,存放其直接前驱节点和直接后续节点的地址信息
- 循环链表:最后一个及地点的引用域存放其头引用的值。
2.4 顺序表与链表
- 顺序表:随机存储,查找效率高,但插入和删除需要移动大量元素,效率低
- 联播啊:存储空间不要求连续,插入、删除效率高,但查找需要从头引用遍历链表,效率低。
2.5 练习题
2.5.1 合并两个有序链表
https://leetcode-cn.com/problems/merge-two-sorted-lists/
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
public class Solution {
public ListNode MergeTwoLists(ListNode l1, ListNode l2) {
var newList = new ListNode(0);
//链表的节点
var node = newList;
while(l1 != null && l2 != null)
{
if(l1.val <l2.val)
{
node.next = l1;
l1 = l1.next;
}
else
{
node.next = l2;
l2 = l2.next;
}
//更新节点
node = node.next;
}
if(l1 != null)
node.next = l1;
if(l2 != null)
node.next = l2;
return newList.next;
}
}
2.5.2 删除链表的倒数第N个节点
https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
public ListNode RemoveNthFromEnd(ListNode head, int n) {
int len = 1;
var aNode = head;
var bNode = head;
while(aNode.next != null)
{
if(len > n)
{
bNode = bNode.next;
}
aNode = aNode.next;
len++;
}
if(n ==len) return head.next;
bNode.next = bNode.next.next;
return head;
}
2.5.3 旋转链表
https://leetcode-cn.com/problems/rotate-list/
给定一个链表,旋转链表,将链表每个节点向右移动k个位置,其中k是非负数。
public ListNode RotateRight(ListNode head, int k)
{
if (head == null || head.next == null) return head;
ListNode aNode = head;
int len = 0;
while(k > 0 && aNode != null)
{
aNode = aNode.next;
len++;
k--;
}
if(aNode == null)
{
k = k % len; //余数
aNode = head;
while(k > 0)
{
aNode = aNode.next;
k--;
}
}
ListNode bNode = head;
if(k == 0)
{
while(aNode.next != null)
{
aNode = aNode.next;
bNode = bNode.next;
}
}
aNode.next = head;
head = bNode.next;
bNode.next = null;
return head;
}
3. 栈与递归
3.1 栈 Stack
是操作限定在表的尾端进行的线性表
表为进行插入、删除等操作
表尾称为栈顶top,另一端固定叫栈底bottom
没有数据元素的栈叫空栈Empty Stack
S = (a1,a2,...,an)
a1 栈底元素
an栈顶元素
出入栈顺序:先进后出,后进先出
S=(D,R) D是数据元素的有限集合,R是数据元素之间关系的有限集合
栈的操作:栈顶插入、删除元素,取栈顶元素,判断栈是否为空
3.2 顺序栈
用连续的存储空间来存储栈中的数据元素,称为顺序栈 Sequence Stack 。一维数据存放顺序栈中的数据元素。
public class SeqStack<T>:IStack<T>{
private int maxsize;
private T[] data; //顺序栈中的数据元素
private int top; //顺序栈的栈顶
}
3.3 链栈
链式存储的栈成为链栈Linked Stack
通常用单链表来表示,是单链表的简化
栈顶设在链表头部,不需要头结点
链栈节点类Node
public class Node<T>
{
private T data; //数据域
private Node<T> next; //引用域
}
链栈类LinkStack
public class LinkStack<T>:IStack<T>{
private Node<T> top; //栈顶指示器
private int num; // 栈中结点的个数
}
链栈的基本操作:
- 求链栈的长度 GetLength()
- 清空操作Clear()
- 判断链栈是否为空IsEmpty()
- 入栈操作Push()
- 出战操作Pop()
- 获取链顶结点的值GetTop()
3.4 递归
一个算法直接调用自己或间接调用自己,就称这个算法是诋毁Recursive.
-
调用方式不同:直接递归Direct Recursion、简介递归 Indirect Recursion
-
必须有两个部分:初始部分、递归部分
-
递归调用的理解:通过一系列的自身调用,达到某一终止条件后,在按照嗲用路线逐步返回。
3.5 练习题
3.5.1
根据要求完成车辆重排的程序代码
假设一列货运列车共有n
节车厢,每节车厢将停放在不同的车站。假定n
个车站的编号分别为1
至n
,货运列车按照第n
站至第1
站的次序经过这些车站。车厢的编号与它们的目的地相同。为了便于从列车上卸掉相应的车厢,必须重新排列车厢,使各车厢从前至后按编号1
至n
的次序排列。当所有的车厢都按照这种次序排列时,在每个车站只需卸掉最后一节车厢即可。
我们在一个转轨站里完成车厢的重排工作,在转轨站中有一个入轨、一个出轨和k
个缓冲铁轨(位于入轨和出轨之间)。图(a)给出一个转轨站,其中有k
个(k=3
)缓冲铁轨H1
,H2
和H3
。开始时,n
节车厢的货车从入轨处进入转轨站,转轨结束时各车厢从右到左按照编号1
至n
的次序离开转轨站(通过出轨处)。在图(a)中,n=9
,车厢从后至前的初始次序为5,8,1,7,4,2,9,6,3
。图(b)给出了按所要求的次序重新排列后的结果。
编写算法实现火车车厢的重排,模拟具有n
节车厢的火车“入轨”和“出轨”过程。
class Program
{
static void Main(string[] args)
{
int[] h = new int[]{5, 3, 6, 8, 4, 1, 2,9, 7};
int k = 3;
bool result = Stack.RailRoad(h, k);
while (result == false)
{
Console.WriteLine("缓冲轨道数量不满足使用,请输入数字扩充。");
k = k + Convert.ToInt32(Console.ReadLine());
result = Stack.RailRoad(h, k);
}
}
}
/// <summary>
/// 车厢重排算法
/// 使用入出栈
/// </summary>
public static class Stack
{
public static bool RailRoad(int[] h,int k)
{
int n = h.Length;
int noOut = 1;
Stack<int>[] stacks = new Stack<int>[k];
for (int i = 0; i < k; i++)
stacks[i] = new Stack<int>();
for(int i = 0; i<n;i++)
{
if (noOut == h[i])
{
//符合出轨序号,直接进入出轨
Console.WriteLine("直接出轨:车厢号{0}直接出轨", h[i]);
noOut++;
//判断缓冲轨是否有可以出轨车厢
while (OutPut(stacks, noOut))
noOut++;
}
else
{
//存入缓冲轨
if (!Hold(stacks, h[i]))
return false;
}
}
return true;
}
#region 车厢入缓冲轨
/// <summary>
/// 车厢入缓冲轨
/// </summary>
/// <param name="stacks">缓冲轨栈</param>
/// <param name="no">车厢号</param>
/// <returns></returns>
static bool Hold(Stack<int>[] stacks, int no)
{
int m = 0; //最佳缓冲轨,默认第一轨
int top = 0; //最佳缓冲轨栈顶元素,默认第一轨栈顶元素
for (int i = 0; i < stacks.Length; i++)
{
if (stacks[i].Count == 0)
{
m = top > no ? m: i;
top = no;
break;
}
else
{
if (no < stacks[i].Peek())
{
if ( top == 0 || top > stacks[i].Peek())
{
top = stacks[i].Peek();
m = i;
}
}
}
}
if (top == 0)
return false;
stacks[m].Push(no);
Console.WriteLine("入缓冲轨:车厢号{0}入缓冲轨{1}", no, m + 1);
return true;
}
#endregion
#region 车厢出缓冲轨
/// <summary>
/// 车厢出缓冲轨
/// </summary>
/// <param name="stacks"></param>
/// <param name="no"></param>
/// <returns>是否有车厢从缓冲轨出</returns>
static bool OutPut(Stack<int>[] stacks,int no)
{
for (int i = 0; i < stacks.Length; i++)
{
if (stacks[i].Count == 0)
continue;
if(no == stacks[i].Peek())
{
stacks[i].Pop();
Console.WriteLine("出缓冲轨:车厢号{0}出缓冲轨{1}", no, i + 1);
return true;
}
}
return false;
}
#endregion
}
4 队列 Queue
4.1 队列的定义及运算
- 是插入操作限定在表的尾部而其他操作限定在表的头部进行的线性表
- 当队列中没有数据元素是成为空队列Empty Queue
- 队列的操作是先进先出or 后进后出 (First In First Out / Last In Last Out )
- Q = (D,R) D 是数据元素的有限集合,R是数据元素之间关系的有限集合
- 操作:
队尾插入、队头删除、去队头元素和判断队列是否为空 - 和栈一样,队列的元素是定义在逻辑结构层次上,而运算的具体实现是建立在物理存储结构层次上
- C#中,队列是从IEnumerable
、ICollection和IEnumberable 接口继承而来
public interface IQueu<T>{
int GetLength();
bool IsEmpty();
void Clear();
void In(T item); //将值为item的新数据元素添加到队尾
T Out(); //将队头元素从队列中取出,队列发生变化
T GetFront(); //取队头元素的值,队列不发生变化
}
4.2 顺序队列
用一片连续的存储空间来存储队列中的数据元素,这样的队列成为顺序队列 Sequence Queue ,类似与顺序栈。
- front 队头指示器,rear 队尾指示器
- 当队列为空时,front = rear = -1
- 数据元素入队,rear + 1
- 数据元素出队,front +1
- rear 达到数据上限而front 为-1时,队列为满
4.3 循环顺序队列
将顺序队列堪称时首位相接的循环结构,头尾指示器的关系不变
public class CSeqQueue<T>:IQueue<T>{
private int maxsize;
private T[] data;
private int front;
private int rear;
public int GetLength()
{
return (rear - front + maxsize)%maxsize;
}
public void Clear()
{
front = rear = -1;
}
public bool IsEmpty()
{
if(front == rear)
{
return true;
}
else
{
return false;
}
}
public bool IsFull()
{
// (rear + 1)% maxsize 等于 front
}
public void In(T item)
{
// data[++rear] = item;
}
public T Out()
{
T tmp = default(T);
if(IsEmpty())
{
Console.WriteLine("Queue is empty");
return tmp;
}
tmp = data[++front];
return tmp;
}
public T GetFront()
{
//IsEmpty判断
return data[front+1];
}
4.4 链队列
队列的另一种存储方式时链式存储,称为链队列 Linked Queue
-
通常用单链表来表示,它的实现时单链表的简化
-
链队列长度:GetLength() //return num
-
清空操作:Clear() //front = rear = null; num = 0;
-
链队列是否为空 IsEmpty() // front == rar && num == 0
-
入队操作:In()
Node<T> q = new Node<T>(item);
if(rear == null)
{
front = rear = q;
}
else
{
rear.Next = q;
rear = q;
}
num++;
- 出队操作:Out()
public T Out()
{
if(IsEmpty())
{
Console.WriteLine("Queue is empty");
return default(T);
}
Node<T> p = front;
front = front.Next;
--num;
return p.Data;
}
- 获取链队列头结点的值:GetFront() // return front.Data;
4.5 练习
4.5.1 模拟银行服务完成程序代码。
目前,在以银行营业大厅为代表的窗口行业中大量使用排队(叫号)系统,该系统完全模拟了人群排队全过程,通过取票进队、排队等待、叫号服务等功能,代替了人们站队的辛苦。
排队叫号软件的具体操作流程为:
顾客取服务序号
当顾客抵达服务大厅时,前往放置在入口处旁的取号机,并按一下其上的相应服务按钮,取号机会自动打印出一张服务单。单上显示服务号及该服务号前面正在等待服务的人数。
服务员工呼叫顾客 服务员工只需按一下其柜台上呼叫器的相应按钮,则顾客的服务号就会按顺序的显示在显示屏上,并发出“叮咚”和相关语音信息,提示顾客前往该窗口办事。当一位顾客办事完毕后,柜台服务员工只需按呼叫器相应键,即可自动呼叫下一位顾客。
编写程序模拟上面的工作过程,主要要求如下:
程序运行后,当看到“请点击触摸屏获取号码:”的提示时,只要按回车键,即可显示“您的号码是:XXX,您前面有YYY位”的提示,其中XXX是所获得的服务号码,YYY是在XXX之前来到的正在等待服务的人数。
用多线程技术模拟服务窗口(可模拟多个),具有服务员呼叫顾客的行为,假设每个顾客服务的时间是10000ms,时间到后,显示“请XXX号到ZZZ号窗口!”的提示。其中ZZZ是即将为客户服务的窗口号。
IQueue接口、循环顺序队列、链队列
interface IQueue<T>
{
int GetLength();
bool IsEmpty();
void Clear();
void In(T item);
T Out();
T GetFront();
}
#region 循环顺序队列
/// <summary>
/// 循环顺序队列
/// </summary>
/// <typeparam name="T"></typeparam>
public class CSeqQueue<T> : IQueue<T>
{
private int maxsize;
private T[] data;
private int front;
private int rear;
public T this[int index]
{
get
{
return data[index];
}
set
{
data[index] = value;
}
}
public int MaxSize { get { return maxsize; } set { maxsize = value; } }
public int Front { get { return front; } set { front = value; } }
public int Rear { get { return rear; } set { rear = value; } }
public CSeqQueue(int size)
{
data = new T[size];
maxsize = size;
front = rear = -1;
}
public int GetLength()
{
return (rear - front + maxsize) % maxsize;
}
public void Clear()
{
front = rear = -1;
}
public bool IsEmpty()
{
if (front == rear)
return true;
return false;
}
public bool IsFull()
{
if ((rear + 1) % maxsize == front)
return true;
return false;
}
public void In(T item)
{
if (IsFull())
{
Console.WriteLine("Queue is Full");
return;
}
data[++rear] = item;
}
public T Out()
{
T tmp = default(T);
if (IsEmpty())
{
Console.WriteLine("Queue is empty");
return tmp;
}
tmp = data[++front];
return tmp;
}
public T GetFront()
{
if (IsEmpty())
{
Console.WriteLine("Queue is empty");
return default(T);
}
return data[front + 1];
}
}
#endregion
#region 链队列结点类
/// <summary>
/// 链队列结点类
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T>
{
private T data;
private Node<T> next;
public Node(T val, Node<T> p)
{
data = val;
next = p;
}
public Node(Node<T> p)
{
next = p;
}
public Node(T val)
{
data = val;
next = null;
}
public Node()
{
data = default(T);
next = null;
}
public T Data
{
get { return data; }
set { data = value; }
}
public Node<T> Next
{
get { return next; }
set { next = value; }
}
}
#endregion
#region 链队列
/// <summary>
/// 链队列
/// </summary>
/// <typeparam name="T"></typeparam>
public class LinkQueue<T> : IQueue<T>
{
private Node<T> front;
private Node<T> rear;
private int num; //队列结点个数
public Node<T> Front { get { return front; } set { front = value; } }
public Node<T> Rear { get { return rear; } set { rear = value; } }
public int Num { get { return num; } set { num = value; } }
public LinkQueue()
{
front = rear = null;
num = 0;
}
public int GetLength()
{
return num;
}
public void Clear()
{
front = rear = null;
num = 0;
}
public bool IsEmpty()
{
if((front == rear) && (num == 0))
return true;
return false;
}
public void In(T item)
{
Node<T> q = new Node<T>(item);
if(rear == null)
{
front = rear = q;
}
else
{
rear.Next = q;
rear = q;
}
num++;
}
public T Out()
{
if(IsEmpty())
{
Console.WriteLine("Queue is empty");
return default(T);
}
Node<T> p = front;
front = front.Next;
if(front == null)
{
rear = null;
}
--num;
return p.Data;
}
public T GetFront()
{
if(IsEmpty())
{
Console.WriteLine("Queue is empty");
return default(T);
}
return front.Data;
}
}
#endregion
public interface IBankQueue:IQueue<int>
{
int GetCallNum();
int MaxSize { get; }
}
public class LinkBankQueue:LinkQueue<int>,IBankQueue
{
public int CallNumber { get; set; }
public int MaxSize { get; }
public LinkBankQueue()
{
MaxSize = default(int);
CallNumber = 0;
}
public int GetCallNum()
{
if(IsEmpty() && CallNumber ==0)
{
CallNumber = 1;
}
else
{
CallNumber++;
}
return CallNumber;
}
}
public class CSeqBankQueue:CSeqQueue<int>,IBankQueue
{
public int Callnumber { get; private set; }
//public int MaxSize { get; }
public CSeqBankQueue(int size):base(size)
{
Callnumber = 0;
}
public int GetCallNum()
{
if (IsEmpty() && Callnumber == 0)
Callnumber = 1;
else
Callnumber++;
return Callnumber;
}
}
public class ServiceWindow
{
public IBankQueue Bankq { get; set; }
public void service()
{
while(true)
{
lock(Bankq)
{
Thread.Sleep(10000);
if(!Bankq.IsEmpty())
{
Console.WriteLine();
Console.WriteLine("{2}:请{0}号到{1}号窗口", Bankq.GetFront(), Thread.CurrentThread.Name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Bankq.Out();
}
}
}
}
}
main
class Program
{
/// <summary>
/// 银行排队叫号
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
try
{
IBankQueue bankQueue = null;
Console.WriteLine("请选择存储结构类型:1.顺序队列,2.链队列");
string flag = Console.ReadLine();
switch(flag)
{
case "1":
Console.WriteLine("请输入队列可容纳人数:");
int count = Convert.ToInt32(Console.ReadLine());
bankQueue = new CSeqBankQueue(count);
break;
case "2":
bankQueue = new LinkBankQueue();
break;
}
int windowsnum = 5;
ServiceWindow[] serviceWindows = new ServiceWindow[windowsnum];
Thread[] serviceThread = new Thread[windowsnum];
for (int i = 0; i < windowsnum; i++)
{
serviceWindows[i] = new ServiceWindow();
serviceWindows[i].Bankq = bankQueue;
serviceThread[i] = new Thread(serviceWindows[i].service);
serviceThread[i].Name = (i + 1).ToString();
serviceThread[i].Start();
}
while(true)
{
Console.WriteLine("点击获取号码:");
Console.ReadLine();
if (bankQueue != null && (bankQueue.GetLength() < bankQueue.MaxSize || flag == "2"))
{
int callnumber = bankQueue.GetCallNum();
Console.WriteLine("{2}:您的号码时:{0},前面还有{1}位等待。", callnumber, bankQueue.GetLength(),DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
bankQueue.In(callnumber);
}
else
Console.WriteLine("请重试");
Console.WriteLine();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
5 字符串
5.1 概念
- 串由n(n>=0)字符组成的有限序列
- 连续存储
- C#中,字符串创建后不可修改
5.2 基本操作
- 串长:GetLength()
- 串比较:Compare(StringDS s)
- 子串:SubString(int index, int len)
- 串连接:Concat(StringDS s)
- 串插入:Insert(int index, StringDS s)
- 串删除:Delete(int index, int len)
- 串定位:Index(StringDS s)
5.3 C#中的串
表示一个恒定不变的字符序列集合
不能被其他类继承,直接继承自object
是引用类型
在托管堆上而不是在吸纳从的堆栈上分配空间
对串的所有操作的结果都是生成了新串而没有改变原串
public sealed class String:IComparable,ICloneable,IConvertible,IComparable<string>,IEnumerable<char>,IEnumerable,IEquatable<string>
{
public String(char[] value);
public static bool operator !=(string a, string b);
public static bool operator ==(string a, string b);
public object Clone();
public static int Compare(string strA, string strB);
public iint Compare(string strB);
public static int CompareOrdinal(string strA,string strB);
public static string Concat(string str0, string str1);
public static string Copy(string str);
public void CopyTo(iint sourceIndex,char[] destination, int destinationIndex, int count);
public bool StartsWith(string value, StringComparison comparisonType);
public bool EndsWith(string value, StriingComparison comparisonType);
public static string Format(string format,object arg0);
public int IndexOf(string value);
public string Insert(int startIndex, string value);
public string Remove(int startIndex, int count);
public string Replace(string oldVlaue,string newValue);
public string Substring(int startIndex, int length);
public string ToLower();
public string ToUpper();
public string Trim(params char[] trimChars);
}
5.4 练习题
5.4.1 给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
public int LengthOfLongestSubstring(string s)
{
int start = 0;
int len = 0 ;
int[] str = new int[128];
for(int i = 0;i<s.Length;i++)
{
start = Math.Max(str[s[i]],start);
len = Math.Max(len,i - start +1);
str[s[i]] = i +1;
}
return len;
}
5.4.2 给定一个字符串s和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。输出的顺序不重要, [9,0] 也是有效答案。
from collections import Counter
if not s or not words:return []
one_word = len(words[0])
all_len = len(words) * one_word
n = len(s)
words = Counter(words)
res = []
for i in range(0, n - all_len + 1):
tmp = s[i:i+all_len]
c_tmp = []
for j in range(0, all_len, one_word):
c_tmp.append(tmp[j:j+one_word])
if Counter(c_tmp) == words:
res.append(i)
return res
5.4.3 替换子串得到平衡字符串
https://leetcode-cn.com/problems/replace-the-substring-for-balanced-string/
有一个只含有'Q'
, 'W'
, 'E'
,'R'
四种字符,且长度为 n
的字符串。假如在该字符串中,这四个字符都恰好出现n/4
次,那么它就是一个「平衡字符串」。
给你一个这样的字符串 s
,请通过「替换一个子串」的方式,使原字符串 s
变成一个「平衡字符串」。你可以用和「待替换子串」长度相同的任何其他字符串来完成替换。
请返回待替换子串的最小可能长度。
如果原字符串自身就是一个平衡字符串,则返回 0。
示例1:
输入:s = "QWER"
输出:0
解释:s 已经是平衡的了。
示例2:
输入:s = "QQWE"
输出:1
解释:我们需要把一个 'Q' 替换成 'R',这样得到的 "RQWE" (或 "QRWE") 是平衡的。
示例3:
输入:s = "QQQW"
输出:2
解释:我们可以把前面的 "QQ" 替换成 "ER"。
示例4:
输入:s = "QQQQ"
输出:3
解释:我们可以替换后 3 个 'Q',使 s = "QWER"。
public int BalancedString(string s) {
int n = s.Length;
int num = n/ 4 ; //平均次数
if(n < 4 || n % 4 >0)
return 0;
Dictionary<char, int> dict = new Dictionary<char, int>();
string copyS = String.Copy(s);
int l = copyS.Length;
while(copyS.Length >0)
{
char c =Convert.ToChar(copyS.Substring(0,1));
string str = copyS.Replace(c.ToString(),"");
if(l-str.Length > num)
dict.Add(c,l-str.Length-num);
l = str.Length;
}
int result = 0;
Queue<int> queue = new Queue<int>();
for(int i = 0;i<s.Length;i++)
{
if(dict.ContainsKey(s[i]))
{
dict[s[i]]--;
queue.Enqueue(i);
bool check= true;
foreach(var v in dict.Values)
{
if(v>0)
{
check = false;
break;
}
}
if(check)
{
while(queue.Count != 0)
{
int j = queue.Peek();
queue.Dequeue();
result = Math.Min(result, i - j +1);
dict[s[j]]++;
if(dict[s[j]] >0)
{
break;
}
}
}
}
}
return result;
}